<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
     
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      
<section class="cover">
    
      
      <a class="forkMe" href="https://github.com/Shen-Yu/hexo-theme-ayer"
        target="_blank"><img width="149" height="149" src="/images/forkme.png"
          class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1"></a>
    
  <div class="cover-frame">
    <div class="bg-box">
      <img src="/images/cover1.jpg" alt="image frame" />
    </div>
    <div class="cover-inner text-center text-white">
      <h1><a href="/">Hexo</a></h1>
      <div id="subtitle-box">
        
        <span id="subtitle"></span>
        
      </div>
      <div>
        
      </div>
    </div>
  </div>
  <div class="cover-learn-more">
    <a href="javascript:void(0)" class="anchor"><i class="ri-arrow-down-line"></i></a>
  </div>
</section>



<script src="https://cdn.jsdelivr.net/npm/typed.js@2.0.11/lib/typed.min.js"></script>


<!-- Subtitle -->

  <script>
    try {
      var typed = new Typed("#subtitle", {
        strings: ['面朝大海，春暖花开', '何来天才，唯有苦练', '集中一点，登峰造极'],
        startDelay: 0,
        typeSpeed: 200,
        loop: true,
        backSpeed: 100,
        showCursor: true
      });
    } catch (err) {
      console.log(err)
    }
  </script>
  
<div id="main">
  <section class="outer">
  
  

<div class="notice" style="margin-top:50px">
    <i class="ri-heart-fill"></i>
    <div class="notice-content" id="broad"></div>
</div>
<script type="text/javascript">
    fetch('https://v1.hitokoto.cn')
        .then(response => response.json())
        .then(data => {
            document.getElementById("broad").innerHTML = data.hitokoto;
        })
        .catch(console.error)
</script>

<style>
    .notice {
        padding: 20px;
        border: 1px dashed #e6e6e6;
        color: #969696;
        position: relative;
        display: inline-block;
        width: 100%;
        background: #fbfbfb50;
        border-radius: 10px;
    }

    .notice i {
        float: left;
        color: #999;
        font-size: 16px;
        padding-right: 10px;
        vertical-align: middle;
        margin-top: -2px;
    }

    .notice-content {
        display: initial;
        vertical-align: middle;
    }
</style>
  
  <article class="articles">
    
    
    
    
    <article
  id="post-k8s/kubernetes 常见问题总结"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/k8s/kubernetes%20%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/"
    >kubernetes 常见问题总结.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/k8s/kubernetes%20%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/k8s/">k8s</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="kubernetes-常见问题总结"><a href="#kubernetes-常见问题总结" class="headerlink" title="kubernetes 常见问题总结"></a>kubernetes 常见问题总结</h1><p>如何删除不一致状态下的 rc,deployment,service</p>
<p>在某些情况下,经常发现 kubectl 进程挂起现象,然后在 get 时候发现删了一半,而另外的删除不了</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[root@k8s-master ~]# kubectl get -f fluentd-elasticsearch&#x2F;  </span><br><span class="line">NAME DESIRED CURRENT READY AGE  </span><br><span class="line">rc&#x2F;elasticsearch-logging-v1 0 2 2 15h  </span><br><span class="line">  </span><br><span class="line">NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE  </span><br><span class="line">deploy&#x2F;kibana-logging 0 1 1 1 15h  </span><br><span class="line">Error from server (NotFound): services &quot;elasticsearch-logging&quot; not found  </span><br><span class="line">Error from server (NotFound): daemonsets.extensions &quot;fluentd-es-v1.22&quot; not found  </span><br><span class="line">Error from server (NotFound): services &quot;kibana-logging&quot; not found</span><br></pre></td></tr></table></figure>



<p>删除这些 deployment,service 或者 rc 命令如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">kubectl delete deployment kibana-logging -n kube-system --cascade&#x3D;false  </span><br><span class="line">  </span><br><span class="line">kubectl delete deployment kibana-logging -n kube-system  --ignore-not-found  </span><br><span class="line">  </span><br><span class="line">delete rc elasticsearch-logging-v1 -n kube-system --force now --grace-period&#x3D;0  </span><br><span class="line">1|2删除不了后如何重置etcd</span><br></pre></td></tr></table></figure>



<p>删除不了后如何重置 etcd</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rm -rf &#x2F;var&#x2F;lib&#x2F;etcd&#x2F;*</span><br></pre></td></tr></table></figure>

<p>删除后重新 reboot master 结点。</p>
<p>reset etcd 后需要重新设置网络</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">etcdctl mk &#x2F;atomic.io&#x2F;network&#x2F;config &#39;&#123; &quot;Network&quot;: &quot;192.168.0.0&#x2F;16&quot; &#125;&#39;</span><br></pre></td></tr></table></figure>



<p>启动 apiserver 失败</p>
<p>每次启动都是报如下问题：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">start request repeated too quickly for kube-apiserver.service</span><br></pre></td></tr></table></figure>



<p>但其实不是启动频率问题,需要查看, /var/log/messages,在我的情况中是因为开启    ServiceAccount 后找不到 ca.crt 等文件,导致启动出错</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">May 21 07:56:41 k8s-master kube-apiserver: Flag --port has been deprecated, see --insecure-port instead.  </span><br><span class="line">May 21 07:56:41 k8s-master kube-apiserver: F0521 07:56:41.692480 4299 universal_validation.go:104] Validate server run options failed: unable to load client CA file: open &#x2F;var&#x2F;run&#x2F;kubernetes&#x2F;ca.crt: no such file or directory  </span><br><span class="line">May 21 07:56:41 k8s-master systemd: kube-apiserver.service: main process exited, code&#x3D;exited, status&#x3D;255&#x2F;n&#x2F;a  </span><br><span class="line">May 21 07:56:41 k8s-master systemd: Failed to start Kubernetes API Server.  </span><br><span class="line">May 21 07:56:41 k8s-master systemd: Unit kube-apiserver.service entered failed state.  </span><br><span class="line">May 21 07:56:41 k8s-master systemd: kube-apiserver.service failed.  </span><br><span class="line">May 21 07:56:41 k8s-master systemd: kube-apiserver.service holdoff time over, scheduling restart.  </span><br><span class="line">May 21 07:56:41 k8s-master systemd: start request repeated too quickly for kube-apiserver.service  </span><br><span class="line">May 21 07:56:41 k8s-master systemd: Failed to start Kubernetes API Server.</span><br></pre></td></tr></table></figure>



<p>在部署 fluentd 等日志组件的时候,很多问题都是因为需要开启 ServiceAccount 选项需要配置安全导致,所以说到底还是需要配置好 ServiceAccount.</p>
<p>出现 Permission denied 情况</p>
<p><strong>在配置 fluentd 时候出现cannot create /var/log/fluentd.log: Permission denied 错误,这是因为没有关掉 SElinux 安全导致。</strong>可以在 /etc/selinux/config 中将 SELINUX=enforcing 设置成 disabled,然后 reboot</p>
<p>基于 ServiceAccount 的配置</p>
<p>首先生成各种需要的 keys,k8s-master 需替换成 master 的主机名.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">openssl genrsa -out ca.key 2048  </span><br><span class="line">openssl req -x509 -new -nodes -key ca.key -subj &quot;&#x2F;CN&#x3D;k8s-master&quot; -days 10000 -out ca.crt  </span><br><span class="line">openssl genrsa -out server.key 2048  </span><br><span class="line">  </span><br><span class="line">echo subjectAltName&#x3D;IP:10.254.0.1 &gt; extfile.cnf  </span><br><span class="line">  </span><br><span class="line">#ip由下述命令决定  </span><br><span class="line">  </span><br><span class="line">#kubectl get services --all-namespaces |grep &#39;default&#39;|grep &#39;kubernetes&#39;|grep &#39;443&#39;|awk &#39;&#123;print $3&#125;&#39;  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">openssl req -new -key server.key -subj &quot;&#x2F;CN&#x3D;k8s-master&quot; -out server.csr  </span><br><span class="line">  </span><br><span class="line">openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out server.crt -days 10000</span><br></pre></td></tr></table></figure>



<p>如果修改 /etc/kubernetes/apiserver 的配置文件参数的话,通过 systemctl start kube-apiserver 启动失败,出错信息为:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Validate server run options failed: unable to load client CA file: open &#x2F;root&#x2F;keys&#x2F;ca.crt: permission denied</span><br></pre></td></tr></table></figure>



<p>但可以通过命令行启动 API Server</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;usr&#x2F;bin&#x2F;kube-apiserver --logtostderr&#x3D;true --v&#x3D;0 --etcd-servers&#x3D;http:&#x2F;&#x2F;k8s-master:2379 --address&#x3D;0.0.0.0 --port&#x3D;8080 --kubelet-port&#x3D;10250 --allow-privileged&#x3D;true --service-cluster-ip-range&#x3D;10.254.0.0&#x2F;16 --admission-control&#x3D;ServiceAccount --insecure-bind-address&#x3D;0.0.0.0 --client-ca-file&#x3D;&#x2F;root&#x2F;keys&#x2F;ca.crt --tls-cert-file&#x3D;&#x2F;root&#x2F;keys&#x2F;server.crt --tls-private-key-file&#x3D;&#x2F;root&#x2F;keys&#x2F;server.key --basic-auth-file&#x3D;&#x2F;root&#x2F;keys&#x2F;basic_auth.csv --secure-port&#x3D;443 &amp;&gt;&gt; &#x2F;var&#x2F;log&#x2F;kubernetes&#x2F;kube-apiserver.log &amp;</span><br></pre></td></tr></table></figure>



<p>命令行启动 Controller-manager</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;usr&#x2F;bin&#x2F;kube-controller-manager --logtostderr&#x3D;true --v&#x3D;0 --master&#x3D;http:&#x2F;&#x2F;k8s-master:8080 --root-ca-file&#x3D;&#x2F;root&#x2F;keys&#x2F;ca.crt --service-account-private-key-file&#x3D;&#x2F;root&#x2F;keys&#x2F;server.key &amp; &gt;&gt;&#x2F;var&#x2F;log&#x2F;kubernetes&#x2F;kube-controller-manage.log</span><br></pre></td></tr></table></figure>





<p>ETCD 启动不起来-问题&lt;1&gt;</p>
<p>etcd 是 kubernetes 集群的 zookeeper 进程，几乎所有的 service 都依赖于 etcd  的启动，比如 flanneld,apiserver,docker…..</p>
<p><strong>在启动 etcd 是报错日志如下：</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">May 24 13:39:09 k8s-master systemd: Stopped Flanneld overlay address etcd agent.  </span><br><span class="line">May 24 13:39:28 k8s-master systemd: Starting Etcd Server...  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: recognized and used environment variable ETCD_ADVERTISE_CLIENT_URLS&#x3D;http:&#x2F;&#x2F;etcd:2379,http:&#x2F;&#x2F;etcd:4001  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: recognized environment variable ETCD_NAME, but unused: shadowed by corresponding flag  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: recognized environment variable ETCD_DATA_DIR, but unused: shadowed by corresponding flag  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: recognized environment variable ETCD_LISTEN_CLIENT_URLS, but unused: shadowed by corresponding flag  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: etcd Version: 3.1.3  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: Git SHA: 21fdcc6  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: Go Version: go1.7.4  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: Go OS&#x2F;Arch: linux&#x2F;amd64  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: setting maximum number of CPUs to 1, total number of available CPUs is 1  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: the server is already initialized as member before, starting as etcd member...  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: listening for peers on http:&#x2F;&#x2F;localhost:2380  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: listening for client requests on 0.0.0.0:2379  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: listening for client requests on 0.0.0.0:4001  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: recovered store from snapshot at index 140014  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: name &#x3D; master  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: data dir &#x3D; &#x2F;var&#x2F;lib&#x2F;etcd&#x2F;default.etcd  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: member dir &#x3D; &#x2F;var&#x2F;lib&#x2F;etcd&#x2F;default.etcd&#x2F;member  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: heartbeat &#x3D; 100ms  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: election &#x3D; 1000ms  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: snapshot count &#x3D; 10000  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: advertise client URLs &#x3D; http:&#x2F;&#x2F;etcd:2379,http:&#x2F;&#x2F;etcd:4001  </span><br><span class="line">May 24 13:39:28 k8s-master etcd: ignored file 0000000000000001-0000000000012700.wal.broken in wal  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: restarting member 8e9e05c52164694d in cluster cdf818194e3a8c32 at commit index 148905  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: 8e9e05c52164694d became follower at term 12  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: newRaft 8e9e05c52164694d [peers: [8e9e05c52164694d], term: 12, commit: 148905, applied: 140014, lastindex: 148905, lastterm: 12]  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: enabled capabilities for version 3.1  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: added member 8e9e05c52164694d [http:&#x2F;&#x2F;localhost:2380] to cluster cdf818194e3a8c32 from store  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: set the cluster version to 3.1 from store  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: starting server... [version: 3.1.3, cluster version: 3.1]  </span><br><span class="line">May 24 13:39:29 k8s-master etcd: raft save state and entries error: open &#x2F;var&#x2F;lib&#x2F;etcd&#x2F;default.etcd&#x2F;member&#x2F;wal&#x2F;0.tmp: is a directory  </span><br><span class="line">May 24 13:39:29 k8s-master systemd: etcd.service: main process exited, code&#x3D;exited, status&#x3D;1&#x2F;FAILURE  </span><br><span class="line">May 24 13:39:29 k8s-master systemd: Failed to start Etcd Server.  </span><br><span class="line">May 24 13:39:29 k8s-master systemd: Unit etcd.service entered failed state.  </span><br><span class="line">May 24 13:39:29 k8s-master systemd: etcd.service failed.  </span><br><span class="line">May 24 13:39:29 k8s-master systemd: etcd.service holdoff time over, scheduling restart.</span><br></pre></td></tr></table></figure>



<p><strong>核心语句：</strong></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">raft save state and entries error: open &#x2F;var&#x2F;lib&#x2F;etcd&#x2F;default.etcd&#x2F;member&#x2F;wal&#x2F;0.tmp: is a directory</span><br></pre></td></tr></table></figure>

<p>进入相关目录，删除 0.tmp,然后就可以启动啦!</p>
<p>ETCD启动不起来-超时问题&lt;2&gt;</p>
<p>问题背景：当前部署了 3 个 etcd 节点，突然有一天 3 台集群全部停电宕机了。重新启动之后发现 K8S 集群是可以正常使用的，但是检查了一遍组件之后，发现有一个节点的 etcd 启动不了。</p>
<p>经过一遍探查，发现时间不准确，通过以下命令 <strong>ntpdate ntp.aliyun.com</strong> 重新将时间调整正确，重新启动 etcd，发现还是起不来，报错如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: etcd Version: 3.3.13  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: Git SHA: 98d3084  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: Go Version: go1.10.8  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: Go OS&#x2F;Arch: linux&#x2F;amd64  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: setting maximum number of CPUs to 4, total number of available CPUs is 4  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: the server is already initialized as member before, starting as etcd member  </span><br><span class="line">...  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: peerTLS: cert &#x3D; &#x2F;opt&#x2F;etcd&#x2F;ssl&#x2F;server.pem, key &#x3D; &#x2F;opt&#x2F;etcd&#x2F;ssl&#x2F;server-key.pe  </span><br><span class="line">m, ca &#x3D; , trusted-ca &#x3D; &#x2F;opt&#x2F;etcd&#x2F;ssl&#x2F;ca.pem, client-cert-auth &#x3D; false, crl-file &#x3D;  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: listening for peers on https:&#x2F;&#x2F;192.168.25.226:2380  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: The scheme of client url http:&#x2F;&#x2F;127.0.0.1:2379 is HTTP while peer key&#x2F;cert  </span><br><span class="line">files are presented. Ignored key&#x2F;cert files.  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: listening for client requests on 127.0.0.1:2379  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: listening for client requests on 192.168.25.226:2379  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3248]: member 9c166b8b7cb6ecb8 has already been bootstrapped  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 systemd[1]: etcd.service: main process exited, code&#x3D;exited, status&#x3D;1&#x2F;FAILURE  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 systemd[1]: Failed to start Etcd Server.  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 systemd[1]: Unit etcd.service entered failed state.  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 systemd[1]: etcd.service failed.  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 systemd[1]: etcd.service failed.  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 systemd[1]: etcd.service holdoff time over, scheduling restart.  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 systemd[1]: Starting Etcd Server...  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_NAME, but unused: shadowed by correspo  </span><br><span class="line">nding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_DATA_DIR, but unused: shadowed by corr  </span><br><span class="line">esponding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_LISTEN_PEER_URLS, but unused: shadowed  </span><br><span class="line"> by corresponding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_LISTEN_CLIENT_URLS, but unused: shadow  </span><br><span class="line">ed by corresponding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_INITIAL_ADVERTISE_PEER_URLS, but unuse  </span><br><span class="line">d: shadowed by corresponding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_ADVERTISE_CLIENT_URLS, but unused: sha  </span><br><span class="line">dowed by corresponding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_INITIAL_CLUSTER, but unused: shadowed  </span><br><span class="line">by corresponding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_INITIAL_CLUSTER_TOKEN, but unused: sha  </span><br><span class="line">dowed by corresponding flag  </span><br><span class="line">Mar 05 14:27:15 k8s-node2 etcd[3258]: recognized environment variable ETCD_INITIAL_CLUSTER_STATE, but unused: sha  </span><br><span class="line">dowed by corresponding flag</span><br></pre></td></tr></table></figure>



<p><strong>解决方法：</strong></p>
<p>检查日志发现并没有特别明显的错误，根据经验来讲，etcd 节点坏掉一个其实对集群没有大的影响，这时集群已经可以正常使用了，但是这个坏掉的 etcd 节点并没有启动，解决方法如下：</p>
<ol>
<li><p>进入 etcd 的数据存储目录进行备份</p>
<p>备份原有数据：</p>
<p>cd /var/lib/etcd/default.etcd/member/</p>
<p>cp *  /data/bak/</p>
</li>
<li><p>删除这个目录下的所有数据文件</p>
<p>rm -rf /var/lib/etcd/default.etcd/member/*</p>
</li>
<li><p>停止另外两台 etcd 节点，因为 etcd 节点启动时需要所有节点一起启动，启动成功后即可使用。</p>
</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">master 节点  </span><br><span class="line">systemctl stop etcd  </span><br><span class="line">systemctl restart etcd  </span><br><span class="line">  </span><br><span class="line">node1 节点  </span><br><span class="line">systemctl stop etcd  </span><br><span class="line">systemctl restart etcd  </span><br><span class="line">  </span><br><span class="line">node2 节点  </span><br><span class="line">systemctl stop etcd  </span><br><span class="line">systemctl restart etcd</span><br></pre></td></tr></table></figure>



<p>CentOS下配置主机互信</p>
<ul>
<li>在每台服务器需要建立主机互信的用户名执行以下命令生成公钥/密钥，默认回车即可</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa</span><br></pre></td></tr></table></figure>

<p>可以看到生成个公钥的文件</p>
<ul>
<li>互传公钥，第一次需要输入密码，之后就OK了</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-copy-id -i &#x2F;root&#x2F;.ssh&#x2F;id_rsa.pub root@192.168.199.132 (-p 2222)</span><br></pre></td></tr></table></figure>

<p>-p 端口 默认端口不加 -p，如果更改过端口，就得加上 -p . 可以看到是在 .ssh/ 下生成了个 authorized_keys 的文件，记录了能登陆这台服务器的其他服务器的公钥</p>
<ul>
<li>测试看是否能登陆</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh 192.168.199.132 (-p 2222)</span><br></pre></td></tr></table></figure>



<p>CentOS 主机名的修改</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hostnamectl set-hostname k8s-master1</span><br></pre></td></tr></table></figure>



<p>Virtualbox 实现 CentOS 复制和粘贴功能</p>
<p>如果不安装或者不输出，可以将 update 修改成 install 再运行</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">yum install update  </span><br><span class="line">yum update kernel  </span><br><span class="line">yum update kernel-devel  </span><br><span class="line">yum install kernel-headers  </span><br><span class="line">yum install gcc  </span><br><span class="line">yum install gcc make</span><br></pre></td></tr></table></figure>



<p>运行完后 sh VBoxLinuxAdditions.run</p>
<p>删除 Pod 一直处于 Terminating 状态</p>
<p>可以通过下面命令强制删除</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl delete pod NAME --grace-period&#x3D;0 --force</span><br></pre></td></tr></table></figure>



<hr>
<p>删除 namespace 一直处于 Terminating 状态</p>
<p>可以通过以下脚本强制删除</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">[root@k8s-master1 k8s]# cat delete-ns.sh  </span><br><span class="line">#!&#x2F;bin&#x2F;bash  </span><br><span class="line">set -e  </span><br><span class="line">  </span><br><span class="line">useage()&#123;  </span><br><span class="line">    echo &quot;useage:&quot;  </span><br><span class="line">    echo &quot; delns.sh NAMESPACE&quot;  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line">if [ $# -lt 1 ];then  </span><br><span class="line">    useage  </span><br><span class="line">    exit  </span><br><span class="line">fi  </span><br><span class="line">  </span><br><span class="line">NAMESPACE&#x3D;$1  </span><br><span class="line">JSONFILE&#x3D;$&#123;NAMESPACE&#125;.json  </span><br><span class="line">kubectl get ns &quot;$&#123;NAMESPACE&#125;&quot; -o json &gt; &quot;$&#123;JSONFILE&#125;&quot;  </span><br><span class="line">vi &quot;$&#123;JSONFILE&#125;&quot;  </span><br><span class="line">curl -k -H &quot;Content-Type: application&#x2F;json&quot; -X PUT --data-binary @&quot;$&#123;JSONFLE&#125;&quot; \  </span><br><span class="line">    http:&#x2F;&#x2F;127.0.0.1:8001&#x2F;api&#x2F;v1&#x2F;namespaces&#x2F;&quot;$&#123;NAMESPACE&#125;&quot;&#x2F;finalize</span><br></pre></td></tr></table></figure>



<p>容器包含有效的 CPU/内存 requests 且没有指定 limits 可能会出现什么问题？</p>
<p>下面我们创建一个对应的容器，该容器只有 requests 设定，但是没有 limits 设定，</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">- name: busybox-cnt02  </span><br><span class="line">    image: busybox  </span><br><span class="line">    command: [&quot;&#x2F;bin&#x2F;sh&quot;]  </span><br><span class="line">    args: [&quot;-c&quot;, &quot;while true; do echo hello from cnt02; sleep 10;done&quot;]  </span><br><span class="line">    resources:  </span><br><span class="line">      requests:  </span><br><span class="line">        memory: &quot;100Mi&quot;  </span><br><span class="line">        cpu: &quot;100m&quot;</span><br></pre></td></tr></table></figure>



<p><strong>这个容器创建出来会有什么问题呢？</strong><br>其实对于正常的环境来说没有什么问题，但是对于资源型 pod 来说，如果有的容器没有设定 limit 限制，资源会被其他的 pod 抢占走，可能会造成容器应用失败的情况。可以通过 limitrange 策略来去匹配，让 pod 自动设定，前提是要提前配置好limitrange 规则。</p>
<p>- END -</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>


<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>


<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line"> 推荐阅读   </span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>


<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>


<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line">[DevOps 工程师该懂些什么？](http:&#x2F;&#x2F;mp.weixin.qq.com&#x2F;s?__biz&#x3D;MzAwNTM5Njk3Mw&#x3D;&#x3D;&amp;mid&#x3D;2247494357&amp;idx&#x3D;1&amp;sn&#x3D;071829bb4c381ce2c286977c6ef0cc28&amp;chksm&#x3D;9b1fea57ac6863418063de447915c53d8094ea57e3056d063b24a7e8f434b1d903b14cda1e7e&amp;scene&#x3D;21#wechat_redirect)</span><br><span class="line"></span><br><span class="line">[Linux系统常用命令速查手册](http:&#x2F;&#x2F;mp.weixin.qq.com&#x2F;s?__biz&#x3D;MzAwNTM5Njk3Mw&#x3D;&#x3D;&amp;mid&#x3D;2247494305&amp;idx&#x3D;1&amp;sn&#x3D;35ed91a64a80571d138b016c5f42b11e&amp;chksm&#x3D;9b1fea23ac686335a9e0d9f045af6f79d941a09783e1d5670386aafe05340018373c9de1c938&amp;scene&#x3D;21#wechat_redirect)</span><br><span class="line"></span><br><span class="line">[SpringCloud微服务项目运维必知必会](http:&#x2F;&#x2F;mp.weixin.qq.com&#x2F;s?__biz&#x3D;MzAwNTM5Njk3Mw&#x3D;&#x3D;&amp;mid&#x3D;2247493425&amp;idx&#x3D;1&amp;sn&#x3D;6c0bf357e15af70860c71c66cee43106&amp;chksm&#x3D;9b1fe7b3ac686ea5e7f4780c4d0ef4bf5e4764345991d5a02a34d5a9ac1c8fbb346d75d5f8b5&amp;scene&#x3D;21#wechat_redirect)</span><br><span class="line"></span><br><span class="line">[Python 自动创建 Grafana 仪表板](http:&#x2F;&#x2F;mp.weixin.qq.com&#x2F;s?__biz&#x3D;MzAwNTM5Njk3Mw&#x3D;&#x3D;&amp;mid&#x3D;2247493257&amp;idx&#x3D;1&amp;sn&#x3D;938ac18223d0c3c7e55d217152e29458&amp;chksm&#x3D;9b1fe60bac686f1d000512e4665edb4fcdefec99468ed53659bbfddfad50e2666c5a5e94f9b1&amp;scene&#x3D;21#wechat_redirect)  </span><br><span class="line"></span><br><span class="line">[Shell文本处理三剑客：grep、sed、awk](http:&#x2F;&#x2F;mp.weixin.qq.com&#x2F;s?__biz&#x3D;MzAwNTM5Njk3Mw&#x3D;&#x3D;&amp;mid&#x3D;2247492280&amp;idx&#x3D;1&amp;sn&#x3D;40aa2a9dbb996ccb6eb09c4ee95419ff&amp;chksm&#x3D;9b1fe23aac686b2c6395bb4fa2d7636cb9381e7498fa5ec01936ded503120263db446fcb816c&amp;scene&#x3D;21#wechat_redirect)</span><br><span class="line"></span><br><span class="line">[搭建一套高可用的Harbor容器镜像仓库](http:&#x2F;&#x2F;mp.weixin.qq.com&#x2F;s?__biz&#x3D;MzAwNTM5Njk3Mw&#x3D;&#x3D;&amp;mid&#x3D;2247494266&amp;idx&#x3D;1&amp;sn&#x3D;51434ab592d8010ec8d90c2664dbe922&amp;chksm&#x3D;9b1feaf8ac6863ee0229cfface65c710dc76045c2da248445639563e4b687014c8649745acfe&amp;scene&#x3D;21#wechat_redirect)</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzAwNTM5Njk3Mw==&mid=2247491271&idx=1&sn=adcd0c5b32ce81d49aaf15c150133954&chksm=9b1c1e45ac6b97536d61299ca3bfc00ab1db7f589101aee1d86ff74304c61f73921920cb8796&scene=21#wechat_redirect">31天Kubernetes集训营，拿下CKA全球认证！</a><img src=""></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>




<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br></pre></td></tr></table></figure>


<p><img src=""></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">点亮，服务器三年不宕机![](data:image&#x2F;gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg&#x3D;&#x3D;)</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure> 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/k8s/" rel="tag">k8s</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-k8s/使用Kubeadm设置Kubernetes集群的高可用性"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/k8s/%E4%BD%BF%E7%94%A8Kubeadm%E8%AE%BE%E7%BD%AEKubernetes%E9%9B%86%E7%BE%A4%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7/"
    >使用Kubeadm设置Kubernetes集群的高可用性.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/k8s/%E4%BD%BF%E7%94%A8Kubeadm%E8%AE%BE%E7%BD%AEKubernetes%E9%9B%86%E7%BE%A4%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/k8s/">k8s</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="使用Kubeadm设置Kubernetes集群的高可用性"><a href="#使用Kubeadm设置Kubernetes集群的高可用性" class="headerlink" title="使用Kubeadm设置Kubernetes集群的高可用性"></a>使用Kubeadm设置Kubernetes集群的高可用性</h1><p>当我们为生产环境在本地设置Kubernetes（k8s）集群时，建议以高可用性部署它。高可用性意味着在HA中安装Kubernetes主节点或工作节点。在本文中，我将演示如何使用kubeadm实用程序设置高可用性的Kubernetes集群。</p>
<p><strong>为了进行演示，我使用了五个具有以下详细信息的CentOS 7系统：</strong></p>
<ul>
<li>k8s-master-1 –最低CentOS 7 – 192.168.1.40 – 2GB RAM，2vCPU，40 GB磁盘</li>
<li>k8s-master-2 –最低CentOS 7 – 192.168.1.41 – 2GB RAM，2vCPU，40 GB磁盘</li>
<li>k8s-master-3 –最低CentOS 7 – 192.168.1.42 – 2GB RAM，2vCPU，40 GB磁盘</li>
<li>k8s-worker-1 –最低CentOS 7 – 192.168.1.43 – 2GB RAM，2vCPU，40 GB磁盘</li>
<li>k8s-worker-2 –最低CentOS 7 – 192.168.1.44 – 2GB RAM，2vCPU，40 GB磁盘</li>
</ul>
<p><img src="http://iubest.gitee.io/pic/640-1601017115245.png" alt="img"></p>
<p>注意：etcd集群也可以在主节点之外形成，但是为此我们需要额外的硬件，因此我将etcd安装在主节点内。</p>
<p>设置K8s集群高可用的最低要求：</p>
<ul>
<li>在所有主节点和工作节点上安装Kubeadm，kubelet和kubectl</li>
<li>主节点和工作节点之间的网络连接</li>
<li>所有节点上的Internet连接</li>
<li>根凭证或所有节点上的sudo特权用户</li>
</ul>
<p>让我们跳过安装和配置步骤。</p>
<h4 id="步骤1-设置主机名并在-etc-hosts文件中添加条目"><a href="#步骤1-设置主机名并在-etc-hosts文件中添加条目" class="headerlink" title="步骤1. 设置主机名并在/etc/hosts文件中添加条目"></a>步骤1. 设置主机名并在/etc/hosts文件中添加条目</h4><p>运行<code>hostnamectl</code>命令在每个节点上设置主机名，例如，示例显示K8s master节点:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ hostnamectl set-hostname &quot;k8s-master-1&quot;</span><br><span class="line">$ exec bash</span><br></pre></td></tr></table></figure>

<p>同样，在其余节点上运行上述命令并设置它们各自的主机名。在所有主节点和工作节点上都设置了主机名之后，请在所有节点上的<code>/etc/hosts</code>文件中添加以下条目。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.40   k8s-master-1</span><br><span class="line">192.168.1.41   k8s-master-2</span><br><span class="line">192.168.1.42   k8s-master-3</span><br><span class="line">192.168.1.43   k8s-worker-1</span><br><span class="line">192.168.1.44   k8s-worker-2</span><br><span class="line">192.168.1.45   vip-k8s-master</span><br></pre></td></tr></table></figure>

<p>我在主机文件中使用了另一个条目“ 192.168.1.45 vip-k8s-master”，因为我将在配置haproxy并在所有主节点上保持连接状态时使用此IP和主机名。该IP将用作kube-apiserver 负载均衡器ip。所有的kube-apiserver请求都将到达此IP，然后请求将分发到后端实际的kube-apiserver节点上。</p>
<h4 id="步骤2-在所有主-工作节点上安装和配置Keepalive和HAProxy"><a href="#步骤2-在所有主-工作节点上安装和配置Keepalive和HAProxy" class="headerlink" title="步骤2.在所有主/工作节点上安装和配置Keepalive和HAProxy"></a>步骤2.在所有主/工作节点上安装和配置Keepalive和HAProxy</h4><p>使用以下<code>yum</code>命令在每个主节点上安装keepalived和haproxy:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum install haproxy keepalived -y</span><br></pre></td></tr></table></figure>

<p>首先在k8s-master-1上配置Keepalived，创建<code>check_apiserver.sh</code>脚本将获得以下内容：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ sudo vi &#x2F;etc&#x2F;keepalived&#x2F;check_apiserver.sh</span><br><span class="line">#!&#x2F;bin&#x2F;sh</span><br><span class="line">APISERVER_VIP&#x3D;192.168.1.45</span><br><span class="line">APISERVER_DEST_PORT&#x3D;6443</span><br><span class="line"></span><br><span class="line">errorExit() &#123;</span><br><span class="line">    echo &quot;*** $*&quot; 1&gt;&amp;2</span><br><span class="line">    exit 1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">curl --silent --max-time 2 --insecure https:&#x2F;&#x2F;localhost:$&#123;APISERVER_DEST_PORT&#125;&#x2F; -o &#x2F;dev&#x2F;null || errorExit &quot;Error GET https:&#x2F;&#x2F;localhost:$&#123;APISERVER_DEST_PORT&#125;&#x2F;&quot;</span><br><span class="line">if ip addr | grep -q $&#123;APISERVER_VIP&#125;; then</span><br><span class="line">    curl --silent --max-time 2 --insecure https:&#x2F;&#x2F;$&#123;APISERVER_VIP&#125;:$&#123;APISERVER_DEST_PORT&#125;&#x2F; -o &#x2F;dev&#x2F;null || errorExit &quot;Error GET https:&#x2F;&#x2F;$&#123;APISERVER_VIP&#125;:$&#123;APISERVER_DEST_PORT&#125;&#x2F;&quot;</span><br><span class="line">fi</span><br></pre></td></tr></table></figure>

<p>保存并退出文件，设置可执行权限：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo chmod + x &#x2F;etc&#x2F;keepalived&#x2F;check_apiserver.sh</span><br></pre></td></tr></table></figure>

<p>备份<code>keepalived.conf</code>文件，然后清空该文件。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ sudo cp &#x2F;etc&#x2F;keepalived&#x2F;keepalived.conf &#x2F;etc&#x2F;keepalived&#x2F;keepalived.conf-org</span><br><span class="line">[kadmin@k8s-master-1 ~]$ sudo sh -c &#39;&gt; &#x2F;etc&#x2F;keepalived&#x2F;keepalived.conf&#39;</span><br></pre></td></tr></table></figure>

<p>现在将以下内容粘贴到<code>/etc/keepalived/keepalived.conf</code>文件中</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ sudo vi &#x2F;etc&#x2F;keepalived&#x2F;keepalived.conf</span><br><span class="line">! &#x2F;etc&#x2F;keepalived&#x2F;keepalived.conf</span><br><span class="line">! Configuration File for keepalived</span><br><span class="line">global_defs &#123;</span><br><span class="line">    router_id LVS_DEVEL</span><br><span class="line">&#125;</span><br><span class="line">vrrp_script check_apiserver &#123;</span><br><span class="line">  script &quot;&#x2F;etc&#x2F;keepalived&#x2F;check_apiserver.sh&quot;</span><br><span class="line">  interval 3</span><br><span class="line">  weight -2</span><br><span class="line">  fall 10</span><br><span class="line">  rise 2</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">vrrp_instance VI_1 &#123;</span><br><span class="line">    state MASTER</span><br><span class="line">    interface enp0s3</span><br><span class="line">    virtual_router_id 151</span><br><span class="line">    priority 255</span><br><span class="line">    authentication &#123;</span><br><span class="line">        auth_type PASS</span><br><span class="line">        auth_pass P@##D321!</span><br><span class="line">    &#125;</span><br><span class="line">    virtual_ipaddress &#123;</span><br><span class="line">        192.168.1.45&#x2F;24</span><br><span class="line">    &#125;</span><br><span class="line">    track_script &#123;</span><br><span class="line">        check_apiserver</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>保存并关闭文件。</p>
<p>注意：对于master-2和3节点，仅需要更改此文件的两个参数。主节点2和3的状态将变为<code>SLAVE</code>，优先级分别为254和253。</p>
<p>在k8s-master-1节点上配置HAProxy，编辑其配置文件并添加以下内容：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ sudo cp &#x2F;etc&#x2F;haproxy&#x2F;haproxy.cfg &#x2F;etc&#x2F;haproxy&#x2F;haproxy.cfg-org</span><br></pre></td></tr></table></figure>

<p>删除默认部分之后的所有行，并添加以下行</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ sudo vi &#x2F;etc&#x2F;haproxy&#x2F;haproxy.cfg</span><br><span class="line">#---------------------------------------------------------------------</span><br><span class="line"># apiserver frontend which proxys to the masters</span><br><span class="line">#---------------------------------------------------------------------</span><br><span class="line">frontend apiserver</span><br><span class="line">    bind *:8443</span><br><span class="line">    mode tcp</span><br><span class="line">    option tcplog</span><br><span class="line">    default_backend apiserver</span><br><span class="line">#---------------------------------------------------------------------</span><br><span class="line"># round robin balancing for apiserver</span><br><span class="line">#---------------------------------------------------------------------</span><br><span class="line">backend apiserver</span><br><span class="line">    option httpchk GET &#x2F;healthz</span><br><span class="line">    http-check expect status 200</span><br><span class="line">    mode tcp</span><br><span class="line">    option ssl-hello-chk</span><br><span class="line">    balance     roundrobin</span><br><span class="line">        server k8s-master-1 192.168.1.40:6443 check</span><br><span class="line">        server k8s-master-2 192.168.1.41:6443 check</span><br><span class="line">        server k8s-master-3 192.168.1.42:6443 check</span><br></pre></td></tr></table></figure>

<p>保存并退出文件。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115261.png" alt="img"></p>
<p>现在将这些三个文件（check_apiserver.sh，keepalived.conf和haproxy.cfg）从k8s-master-1复制到k8s-master-2和3</p>
<p>运行以下for循环将这些文件scp复制到master 2和3上；</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ for f in k8s-master-2 k8s-master-3; do scp &#x2F;etc&#x2F;keepalived&#x2F;check_apiserver.sh &#x2F;etc&#x2F;keepalived&#x2F;keepalived.conf root@$f:&#x2F;etc&#x2F;keepalived; scp &#x2F;etc&#x2F;haproxy&#x2F;haproxy.cfg root@$f:&#x2F;etc&#x2F;haproxy; done</span><br></pre></td></tr></table></figure>

<p>注意：别忘了更改我们在上面针对k8s-master-2和3讨论的<code>keepalived.conf</code>文件中的两个参数。</p>
<p>如果防火墙在主节点上运行，则在所有三个主节点上添加以下防火墙规则</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ sudo firewall-cmd --add-rich-rule&#x3D;&#39;rule protocol value&#x3D;&quot;vrrp&quot; accept&#39; --permanent</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;8443&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --reload</span><br></pre></td></tr></table></figure>

<p>现在，使用以下命令在所有三个主节点上启动并启用keepalived和haproxy服务：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo systemctl enable keepalived --now</span><br><span class="line">$ sudo systemctl enable haproxy --now</span><br></pre></td></tr></table></figure>

<p>这些服务成功启动后，请验证是否已在k8s-master-1节点上启用了VIP（虚拟IP），因为我们已在keepalived配置文件中将k8s-master-1标记为MASTER节点。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115394.png" alt="img"></p>
<p>完美，上面的输出确认已在k8s-master-1上启用了VIP。</p>
<h4 id="步骤3-禁用Swap分区，将SELinux设置为主节点和辅助节点的许可规则和防火墙规则"><a href="#步骤3-禁用Swap分区，将SELinux设置为主节点和辅助节点的许可规则和防火墙规则" class="headerlink" title="步骤3. 禁用Swap分区，将SELinux设置为主节点和辅助节点的许可规则和防火墙规则"></a>步骤3. 禁用Swap分区，将SELinux设置为主节点和辅助节点的许可规则和防火墙规则</h4><p>在所有节点（包括工作节点）上禁用交换空间，运行以下命令：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo swapoff -a </span><br><span class="line">$ sudo sed -i &#39;&#x2F; swap &#x2F; s&#x2F;^\(.*\)$&#x2F;#\1&#x2F;g&#39; &#x2F;etc&#x2F;fstab</span><br></pre></td></tr></table></figure>

<p>在所有主节点和工作节点上将SELinux设置为Permissive，运行以下命令，</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo setenforce 0</span><br><span class="line">$ sudo sed -i&#39;s &#x2F; ^ SELINUX &#x3D; enforcing $ &#x2F; SELINUX &#x3D; permissive &#x2F;&#39;&#x2F; etc &#x2F; selinux &#x2F; config</span><br></pre></td></tr></table></figure>

<p>主节点的防火墙规则：</p>
<p>如果防火墙在主节点上运行，则在防火墙中允许以下端口</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115113.webp" alt="img"></p>
<p>所有主节点上运行以下firewall-cmd命令:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;6443&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;2379-2380&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;10250&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;10251&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;10252&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;179&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;4789&#x2F;udp</span><br><span class="line">$ sudo firewall-cmd --reload</span><br><span class="line">$ sudo modprobe br_netfilter</span><br><span class="line">$ sudo sh -c &quot;echo &#39;1&#39; &gt; &#x2F;proc&#x2F;sys&#x2F;net&#x2F;bridge&#x2F;bridge-nf-call-iptables&quot;</span><br><span class="line">$ sudo sh -c &quot;echo &#39;1&#39; &gt; &#x2F;proc&#x2F;sys&#x2F;net&#x2F;ipv4&#x2F;ip_forward&quot;</span><br></pre></td></tr></table></figure>

<p>工作节点的防火墙规则：</p>
<p>如果防火墙在工作节点上运行，则在所有工作程序节点上允许防火墙中的以下端口</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115109.webp" alt="img"></p>
<p>在所有工作节点上运行以下命令:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;10250&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;30000-32767&#x2F;tcp                                                   </span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;179&#x2F;tcp</span><br><span class="line">$ sudo firewall-cmd --permanent --add-port&#x3D;4789&#x2F;udp</span><br><span class="line">$ sudo firewall-cmd --reload</span><br><span class="line">$ sudo modprobe br_netfilter</span><br><span class="line">$ sudo sh -c &quot;echo &#39;1&#39; &gt; &#x2F;proc&#x2F;sys&#x2F;net&#x2F;bridge&#x2F;bridge-nf-call-iptables&quot;</span><br><span class="line">$ sudo sh -c &quot;echo &#39;1&#39; &gt; &#x2F;proc&#x2F;sys&#x2F;net&#x2F;ipv4&#x2F;ip_forward&quot;</span><br></pre></td></tr></table></figure>

<h4 id="步骤4-在主节点和工作节点上安装容器运行（CRI）Docker"><a href="#步骤4-在主节点和工作节点上安装容器运行（CRI）Docker" class="headerlink" title="步骤4. 在主节点和工作节点上安装容器运行（CRI）Docker"></a>步骤4. 在主节点和工作节点上安装容器运行（CRI）Docker</h4><p>在所有主节点和工作节点上安装Docker，运行以下命令</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum install -y yum-utils</span><br><span class="line">$ sudo yum-config-manager --add-repo https:&#x2F;&#x2F;download.docker.com&#x2F;linux&#x2F;centos&#x2F;docker-ce.repo</span><br><span class="line">$ sudo yum install docker-ce -y</span><br></pre></td></tr></table></figure>

<p>运行以下systemctl命令以启动并启用docker服务（在所有主节点和工作节点上也运行此命令）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo systemctl enable docker --now</span><br></pre></td></tr></table></figure>

<p>现在，让我们在下一步中安装kubeadm，kubelet和kubectl</p>
<h4 id="步骤5-安装Kubeadm，kubelet和kubectl"><a href="#步骤5-安装Kubeadm，kubelet和kubectl" class="headerlink" title="步骤5. 安装Kubeadm，kubelet和kubectl"></a>步骤5. 安装Kubeadm，kubelet和kubectl</h4><p>在所有主节点和辅助节点上安装kubeadm，kubelet和kubectl。首先安装这些软件包之前，我们必须配置Kubernetes repo仓库，在每个主节点和工作节点上运行以下命令</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">cat &lt;&lt;EOF | sudo tee &#x2F;etc&#x2F;yum.repos.d&#x2F;kubernetes.repo</span><br><span class="line">[kubernetes]</span><br><span class="line">name&#x3D;Kubernetes</span><br><span class="line">baseurl&#x3D;https:&#x2F;&#x2F;packages.cloud.google.com&#x2F;yum&#x2F;repos&#x2F;kubernetes-el7-\$basearch</span><br><span class="line">enabled&#x3D;1</span><br><span class="line">gpgcheck&#x3D;1</span><br><span class="line">repo_gpgcheck&#x3D;1</span><br><span class="line">gpgkey&#x3D;https:&#x2F;&#x2F;packages.cloud.google.com&#x2F;yum&#x2F;doc&#x2F;yum-key.gpg https:&#x2F;&#x2F;packages.cloud.google.com&#x2F;yum&#x2F;doc&#x2F;rpm-package-key.gpg</span><br><span class="line">exclude&#x3D;kubelet kubeadm kubectl</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure>

<p>现在，在yum命令下面运行以安装这些软件包</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo yum install -y kubelet kubeadm kubectl --disableexcludes&#x3D;kubernetes</span><br></pre></td></tr></table></figure>

<p>运行以下systemctl命令以在所有节点（主节点和工作节点）上启用kubelet服务</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo systemctl enable kubelet --now</span><br></pre></td></tr></table></figure>

<h4 id="步骤6-从第一个主节点初始化Kubernetes集群"><a href="#步骤6-从第一个主节点初始化Kubernetes集群" class="headerlink" title="步骤6. 从第一个主节点初始化Kubernetes集群"></a>步骤6. 从第一个主节点初始化Kubernetes集群</h4><p>现在到第一个主节点终端下执行以下命令</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ sudo kubeadm init --control-plane-endpoint &quot;vip-k8s-master:8443&quot; --upload-certs</span><br></pre></td></tr></table></figure>

<p>在以上命令中， <code>--control-plane-endpoint</code>设置了负载均衡器（kube-apiserver）的dns名称和端口，在我的情况下，dns名称是“vip-k8s-master”，端口是“ 8443”，除了此<code>--upload-certs</code>的选项将自动在主节点之间共享证书。</p>
<p>kubeadm命令的输出如下所示：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115320.png" alt="img"></p>
<p>很好，上面的输出确认Kubernetes集群已成功初始化。在输出中，我们还获得了其他主节点和工作节点加入集群的命令。</p>
<p>注意：建议将此输出复制到文本文件中，以备将来参考。</p>
<p>运行以下命令，以允许本地用户使用kubectl命令与集群进行交互</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ mkdir -p $HOME&#x2F;.kube</span><br><span class="line">[kadmin@k8s-master-1 ~]$ sudo cp -i &#x2F;etc&#x2F;kubernetes&#x2F;admin.conf $HOME&#x2F;.kube&#x2F;config</span><br><span class="line">[kadmin@k8s-master-1 ~]$ sudo chown $(id -u):$(id -g) $HOME&#x2F;.kube&#x2F;config</span><br><span class="line">[kadmin@k8s-master-1 ~]$</span><br></pre></td></tr></table></figure>

<p>现在，让我们部署Pod网络（CNI –容器网络接口），在我的情况下，我将calico插件部署为Pod网络，请按照kubectl命令运行</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ kubectl apply -f https:&#x2F;&#x2F;docs.projectcalico.org&#x2F;v3.14&#x2F;manifests&#x2F;calico.yaml</span><br></pre></td></tr></table></figure>

<p>成功部署Pod网络后，将其余两个主节点添加到群集中。只需从输出中复制用于主节点的命令以加入集群，然后将其粘贴到k8s-master-2和k8s-master-3上，示例如下所示</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-2 ~]$ sudo kubeadm join vip-k8s-master:8443 --token tun848.2hlz8uo37jgy5zqt  --discovery-token-ca-cert-hash sha256:d035f143d4bea38d54a3d827729954ab4b1d9620631ee330b8f3fbc70324abc5 --control-plane --certificate-key a0b31bb346e8d819558f8204d940782e497892ec9d3d74f08d1c0376dc3d3ef4</span><br></pre></td></tr></table></figure>

<p>输出如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115259.png" alt="img"></p>
<p>以上输出确认k8s-master-3也已成功加入集群。让我们从kubectl命令验证节点状态，到master-1节点并执行以下命令</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ kubectl get nodes</span><br><span class="line">NAME           STATUS   ROLES    AGE     VERSION</span><br><span class="line">k8s-master-1   Ready    master   31m     v1.18.6</span><br><span class="line">k8s-master-2   Ready    master   10m     v1.18.6</span><br><span class="line">k8s-master-3   Ready    master   3m47s   v1.18.6</span><br><span class="line">[kadmin@k8s-master-1 ~]$</span><br></pre></td></tr></table></figure>

<p>完美，我们三个主节点均已准备就绪，并加入了集群。</p>
<h4 id="步骤7-将Worker节点加入Kubernetes集群"><a href="#步骤7-将Worker节点加入Kubernetes集群" class="headerlink" title="步骤7. 将Worker节点加入Kubernetes集群"></a>步骤7. 将Worker节点加入Kubernetes集群</h4><p>要将工作节点加入集群，请从输出中复制工作程序节点的命令，并将其粘贴到两个工作程序节点上，示例如下所示：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-worker-1 ~]$ sudo kubeadm join vip-k8s-master:8443 --token tun848.2hlz8uo37jgy5zqt --discovery-token-ca-cert-hash sha256:d035f143d4bea38d54a3d827729954ab4b1d9620631ee330b8f3fbc70324abc5</span><br><span class="line"></span><br><span class="line">[kadmin@k8s-worker-2 ~]$ sudo kubeadm join vip-k8s-master:8443 --token tun848.2hlz8uo37jgy5zqt --discovery-token-ca-cert-hash sha256:d035f143d4bea38d54a3d827729954ab4b1d9620631ee330b8f3fbc70324abc5</span><br></pre></td></tr></table></figure>

<p>输出如下所示：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115393.png" alt="img"></p>
<p>现在转到k8s-master-1节点并在kubectl命令下运行以获取状态工作程序节点：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ kubectl get nodes</span><br><span class="line">NAME           STATUS   ROLES    AGE     VERSION</span><br><span class="line">k8s-master-1   Ready    master   43m     v1.18.6</span><br><span class="line">k8s-master-2   Ready    master   21m     v1.18.6</span><br><span class="line">k8s-master-3   Ready    master   15m     v1.18.6</span><br><span class="line">k8s-worker-1   Ready    &lt;none&gt;   6m11s   v1.18.6</span><br><span class="line">k8s-worker-2   Ready    &lt;none&gt;   5m22s   v1.18.6</span><br><span class="line">[kadmin@k8s-master-1 ~]$</span><br></pre></td></tr></table></figure>

<p>以上输出确认两个工作节点都已加入集群并处于就绪状态。</p>
<p>运行以下命令，以验证在kube-system名称空间中部署的状态。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@k8s-master-1 ~]$ kubectl get pods -n kube-system</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601017115315.webp" alt="img"></p>
<h4 id="步骤8-测试Kubernetes集群的高可用性"><a href="#步骤8-测试Kubernetes集群的高可用性" class="headerlink" title="步骤8. 测试Kubernetes集群的高可用性"></a>步骤8. 测试Kubernetes集群的高可用性</h4><p>让我们尝试使用负载平衡器dns名称和端口从远程计算机（CentOS系统）连接到群集。首先，在远程计算机上，我们必须安装kubectl软件包。运行以下命令来设置kubernetes软件仓库。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">cat &lt;&lt;EOF | sudo tee &#x2F;etc&#x2F;yum.repos.d&#x2F;kubernetes.repo</span><br><span class="line">[kubernetes]</span><br><span class="line">name&#x3D;Kubernetes</span><br><span class="line">baseurl&#x3D;https:&#x2F;&#x2F;packages.cloud.google.com&#x2F;yum&#x2F;repos&#x2F;kubernetes-el7-\$basearch</span><br><span class="line">enabled&#x3D;1</span><br><span class="line">gpgcheck&#x3D;1</span><br><span class="line">repo_gpgcheck&#x3D;1</span><br><span class="line">gpgkey&#x3D;https:&#x2F;&#x2F;packages.cloud.google.com&#x2F;yum&#x2F;doc&#x2F;yum-key.gpg https:&#x2F;&#x2F;packages.cloud.google.com&#x2F;yum&#x2F;doc&#x2F;rpm-package-key.gpg</span><br><span class="line">exclude&#x3D;kubelet kubeadm kubectl</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">$ sudo yum install -y  kubectl --disableexcludes&#x3D;kubernetes</span><br></pre></td></tr></table></figure>

<p>现在在<code>/etc/host</code>文件中添加以下条目:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.45   vip-k8s-master</span><br></pre></td></tr></table></figure>

<p>创建kube目录，并将<code>/etc/kubernetes/admin.conf</code>文件从k8s-master-1节点复制到<code>$HOME/.kube/config</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ mkdir -p $HOME&#x2F;.kube</span><br><span class="line">$ scp root@192.168.1.40:&#x2F;etc&#x2F;kubernetes&#x2F;admin.conf $HOME&#x2F;.kube&#x2F;config</span><br><span class="line">$ sudo chown $(id -u):$(id -g) $HOME&#x2F;.kube&#x2F;config</span><br></pre></td></tr></table></figure>

<p>现在运行“kubectl get nodes”命令，</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@localhost ~]$ kubectl get nodes</span><br><span class="line">NAME           STATUS   ROLES    AGE    VERSION</span><br><span class="line">k8s-master-1   Ready    master   3h5m   v1.18.6</span><br><span class="line">k8s-master-2   Ready    master   163m   v1.18.6</span><br><span class="line">k8s-master-3   Ready    master   157m   v1.18.6</span><br><span class="line">k8s-worker-1   Ready    &lt;none&gt;   148m   v1.18.6</span><br><span class="line">k8s-worker-2   Ready    &lt;none&gt;   147m   v1.18.6</span><br><span class="line">[kadmin@localhost ~]$</span><br></pre></td></tr></table></figure>

<p>让我们创建一个名为nginx-lab的部署，镜像为“ nginx ”，然后将该部署作为类型为“ NodePort ”的服务公开</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@localhost ~]$ kubectl create deployment nginx-lab --image&#x3D;nginx</span><br><span class="line">deployment.apps&#x2F;nginx-lab created</span><br><span class="line">[kadmin@localhost ~]$</span><br><span class="line">[kadmin@localhost ~]$ kubectl get deployments.apps nginx-lab</span><br><span class="line">NAME        READY   UP-TO-DATE   AVAILABLE   AGE</span><br><span class="line">nginx-lab   1&#x2F;1     1            1           59s</span><br><span class="line">[kadmin@localhost ~]$ kubectl get pods</span><br><span class="line">NAME                         READY   STATUS    RESTARTS   AGE</span><br><span class="line">nginx-lab-5df4577d49-rzv9q   1&#x2F;1     Running   0          68s</span><br><span class="line">test-844b65666c-pxpkh        1&#x2F;1     Running   3          154m</span><br><span class="line">[kadmin@localhost ~]$</span><br></pre></td></tr></table></figure>

<p>让我们尝试将副本从1扩到4，运行以下命令:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@localhost ~]$ kubectl scale deployment nginx-lab --replicas&#x3D;4</span><br><span class="line">deployment.apps&#x2F;nginx-lab scaled</span><br><span class="line">[kadmin@localhost ~]$</span><br><span class="line">[kadmin@localhost ~]$ kubectl get deployments.apps nginx-lab</span><br><span class="line">NAME        READY   UP-TO-DATE   AVAILABLE   AGE</span><br><span class="line">nginx-lab   4&#x2F;4     4            4           3m10s</span><br><span class="line">[kadmin@localhost ~]$</span><br></pre></td></tr></table></figure>

<p>现在将部署作为服务公开，运行以下命令:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@localhost ~]$ kubectl expose deployment nginx-lab --name&#x3D;nginx-lab --type&#x3D;NodePort --port&#x3D;80 --target-port&#x3D;80</span><br><span class="line">service&#x2F;nginx-lab exposed</span><br><span class="line">[kadmin@localhost ~]$</span><br></pre></td></tr></table></figure>

<p>获取端口详细信息，并尝试使用curl访问nginx Web服务器</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@localhost ~]$ kubectl get svc nginx-lab</span><br><span class="line">NAME        TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">nginx-lab   NodePort   10.102.32.29   &lt;none&gt;        80:31766&#x2F;TCP   60s</span><br><span class="line">[kadmin@localhost ~]$</span><br></pre></td></tr></table></figure>

<p>要访问nginx Web服务器，我们可以使用任何主节点或工作节点IP和端口作为“ 31766”</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[kadmin@localhost ~]$ curl http:&#x2F;&#x2F;192.168.1.44:31766</span><br></pre></td></tr></table></figure>

<p>输出如下所示：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601017115391.png" alt="img"></p>
<p>完美，这严重了我们已经在CentOS 7服务器上使用kubeadm成功部署了高可用性Kubernetes集群。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/k8s/" rel="tag">k8s</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-k8s/如何用 K8S 搞定 1000 个应用的测试环境"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/k8s/%E5%A6%82%E4%BD%95%E7%94%A8%20K8S%20%E6%90%9E%E5%AE%9A%201000%20%E4%B8%AA%E5%BA%94%E7%94%A8%E7%9A%84%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/"
    >如何用 K8S 搞定 1000 个应用的测试环境.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/k8s/%E5%A6%82%E4%BD%95%E7%94%A8%20K8S%20%E6%90%9E%E5%AE%9A%201000%20%E4%B8%AA%E5%BA%94%E7%94%A8%E7%9A%84%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/k8s/">k8s</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="如何用-K8S-搞定-1000-个应用的测试环境"><a href="#如何用-K8S-搞定-1000-个应用的测试环境" class="headerlink" title="如何用 K8S 搞定 1000 个应用的测试环境"></a>如何用 K8S 搞定 1000 个应用的测试环境</h1><p>到今天为止，信也科技的研发团队已经全部使用星云系统作为开发测试环境了。星云是一套基于 K8S 的环境管理系统，它主要用于管理多个平行的测试环境。它维护这些测试环境，使它们可以稳定工作。星云系统所体现出来的简单、高效已经被大家所认可。我想可以把关于它的开发经历写一写，作为分享。</p>
<h2 id="问题与现状"><a href="#问题与现状" class="headerlink" title="问题与现状"></a>问题与现状</h2><p>差不多是半年以前，基础框架团队接到了一个任务：环境治理。究其原因是开发团队被测试环境的问题困扰，影响了研发效率和进度，急需一套解决方案。</p>
<p>让我们看看当时的情景，信也科技是一家有着10多年历史的金融互联网公司，业务产品非常多，应用数目累计1000+ 的样子，整个系统可以说是非常庞大。有同事形象地形容这套系统，这是一条从新疆到上海的铁路，用户在新疆上车，途径了10来个省（团队）的地盘，最后在上海下车。</p>
<p>可想而知，这条路上无论那个环节出了问题，整条路可能就不通了。当时公司内部有3套比较完整的大环境，分别是 FAT、UAT、PRE。不过 UAT 与 PRE 都不是开发直接使用，所有的开发测试都在使用 FAT 环境，后果就是冲突非常严重，新功能测试上线，旧 Bug 修复，日常组件故障都时时困扰着大家。可以说那个时候 通讯基本靠吼，如果出了问题，有时候必须不同组件、不同团队排查问题，非常影响效率。</p>
<h2 id="核心问题"><a href="#核心问题" class="headerlink" title="核心问题"></a>核心问题</h2><p>现状知道了，核心问题是什么呢？通过分析，我们认为核心问题有2个。</p>
<ol>
<li>稳定的系统需要稳定的组件。只有一套FAT环境，大家都在上面做变更，这造成了整套系统的某些模块没有充分测试，那么模块的不稳定，造成了整个系统的不稳定。</li>
<li>高速发展的业务要求快速迭代组件，不停地进行组件更新。业务的需求多变，业务推进，Bug 修复，新需求要测试，如果没有变更，那么业务无法进行，所以必须去变更组件。</li>
</ol>
<p>最终系统稳定不下来，想稳定就要组件稳定，但是业务高速发展，就要不断引入组件和新版本。这2者矛盾了。</p>
<h3 id="问题的核心是什么？"><a href="#问题的核心是什么？" class="headerlink" title="问题的核心是什么？"></a>问题的核心是什么？</h3><p>资源冲突，只有一套完整环境，资源成为了瓶颈，如果我们多复制几套环境 问题不就解决了吗？</p>
<p>但是问题来了，复制几套环境，如何复制这个环境呢？复制的资源成本和维护成本怎么控制？我们希望复制维护的成本足够的小，其次，多套环境意味着需要不停地将各个环境的应用版本进行同步，否则会出现一个个环境版本老化，劣化，逐渐失去它存在的意义，最终大家又回到一个测试环境的情况。</p>
<p>所以我们最终是这样设计的。</p>
<h2 id="小目标"><a href="#小目标" class="headerlink" title="小目标"></a>小目标</h2><p>根据核心问题的定义，我们的也随之定了2个小目标。</p>
<ol>
<li>提供一个稳定的测试环境 保证整个测试环境的稳定。它会随着生产环境同步 保证它一直是线上最新的。</li>
<li>为每个团队提供多套私有的测试环境，每个团队在私有环境测试不会影响其他团队。</li>
</ol>
<p>在实施目标之前，我们也已经有足够的技术储备和开发规范，首先是容器及 K8S 已经落地，应用已经基本完全容器化，容器的发布流程已经平台化，团队的应用配置也使用了 Apollo 这样的中心配置服务，MQ 等组件支持 Restful 接口，容器具有静态IP分配技术。这些技术条件是项目最终成功的技术基石。</p>
<p>通过努力，星云系统诞生了。利用它，我们操作 K8S 完成了环境的创建、管理、销毁，很好地达成预计的目标。（根据本公司的实际情况，大部分应用都是利用 Restful RPC 进行交互，利用域名进行寻址，一般情况下用 Nginx 进行消息转发，所有设计以此为前提。）</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333907.webp" alt="img"></p>
<h2 id="四大优点"><a href="#四大优点" class="headerlink" title="四大优点"></a>四大优点</h2><ol>
<li>快速地创建测试环境，</li>
<li>应用可在不同环境里复用。</li>
<li>零配置使用系统。</li>
<li>应用无需改造</li>
</ol>
<p>这一切是如何做到的呢？</p>
<h2 id="整体设计"><a href="#整体设计" class="headerlink" title="整体设计"></a>整体设计</h2><p>我们如何利用 K8S 完成这一切的呢？整体组件及流程这样</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333904.webp" alt="img"></p>
<p>我们创建了星云多环境管理平台，用户可以自助在上面创建测试环境，在创建指令发出后，星云平台会把指令转换成一批创建容器的指令，利用现有的容器发布平台进行容器创建（主要利用已有稳定服务）。现有容器发布平台会让 K8S 将指定的镜像，应用按照指定顺序启动，并进行相应的配置。</p>
<p>在一个环境里所有的应用都启动以后，一个测试环境就准备好了，这时候测试人员或者开发人员就可以利用这个独立的测试环境进行开发、测试了。每个环境都是个独立的环境，相互不影响。</p>
<p>在这里我们只是利用 K8S 管理容器，让 K8S 进行 Pod 的管理，并不涉及任何其他服务，对它的依赖降低到了最低。</p>
<h2 id="架构及原理"><a href="#架构及原理" class="headerlink" title="架构及原理"></a>架构及原理</h2><p>星云的设计和实现 从用户的角度看大概是这样的。环境之间是完全隔绝的。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333908.webp" alt="img"></p>
<p>首先我们需要让每个环境正常的运行起来，相互之间不影响，如何实现这一切呢？</p>
<p>在这点上，我们其实参考的是 K8S 本身的架构，K8S 是如何让整个系统运行起来的？</p>
<p>在 K8S 内部有2个很重要的组件 一个是 CoreDNS，一个是 Ingress, 它们分别完成了内部寻址和对外交流这2个重要的功能。在星云里，我们也是借鉴了这一设计思想。</p>
<p>我们在每个新拉起的环境里都预先加入了2个重要的组件，一个是 Dns，另一个是流量网关（Nginx）。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333904.webp" alt="img"></p>
<p>它们为只为单独环境服务，是单个环境的核心。DNS 组件首先启动，它和星云多环境管理平台通讯，获取本测试环境内实例的数据，而网关就是普通的 nginx，它也会和星云管理平台通讯，获得相应的转发策略，按照转发策略工作。</p>
<p>当环境里的普通业务应用启动后，他们会首先从环境内的 DNS 组件里获取信息，这样他们就能进行相互定位，它们之间通过网关转发消息。当用户在外部需要利用这个环境进行测试的时候，测试将测试流量打到网关这个容器上。网关预设的转发策略将流量分发到环境内合适的容器上，让整个测试正常工作。</p>
<h2 id="Dns实现"><a href="#Dns实现" class="headerlink" title="Dns实现"></a>Dns实现</h2><p>Dns 本身的实现其实也不复杂，下图简单的描述了 DNS 的原理，星云多环境管理平台会根据环境里的实例状况，把信息同步到每个环境的 Dns 组件里，同一个站点的记录有优先级的区分。这样就可以定制出我们想要的效果。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333907.webp" alt="img"></p>
<p>环境1里的应用访问 <code>A.com</code> 的时候，会得到 192.168.2.200 这个结果。访问 <code>B.com</code> 的时候会得到 192.168.2.101 这个结果，而访问 <code>C.com</code> 的时候 会得到 172.168.2.4 这个结果。</p>
<p>因为有优先级，有自动同步的数据，也可以用户自由设置，这样我们在星云中心是可以随意设计环境内部的 DNS。</p>
<p>总结成一句话，每个环境里的 DNS 里的记录是星云控制的，大部分是自动的，但是你也可以控制它。</p>
<h2 id="网关实现"><a href="#网关实现" class="headerlink" title="网关实现"></a>网关实现</h2><p>网关的实现的原理也类似，星云控制中心会根据不同的环境，计算出它的网关的规则是什么样子。星云把规则推送到网关服务器上。这样通过网关和 Dns 的配合，星云系统完成控制层面的操作，网关和 DNS 执行数据流层面的操作。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333910.webp" alt="img"></p>
<h2 id="RPC-实现"><a href="#RPC-实现" class="headerlink" title="RPC 实现"></a>RPC 实现</h2><p>在 DNS 和 网关完成以后，我们只是完成初步的环境搭建，还有很多细节需要优化，其中最关键的是 RPC 部分。</p>
<p>因为我们有1000+的实例，如果靠简单的拉起实例，那么一个环境就需要拉起1000个实例，10套环境就有1万+实例，首先资源上这是一个巨大的负载，其次维护这么多环境的维护成本会很高。所以我们需要让应用能在不同的环境复用。</p>
<p>正所谓一图胜千言，整个系统差不多就是下面这张图所描述的场景。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333694.webp" alt="img"></p>
<p>公共基础环境就是 Default 基础环境，它和生产保持一致，我们让它保持稳定。项目环境-1，项目环境-2，项目环境-3 则是星云利用 K8S 的容器管理技术生成的。用户在这里测试新的需求，修复 Bug，相互之间是不会影响的。也不会影响到 FAT 环境的稳定。</p>
<p>我们最终实现的效果大概这样。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333615.webp" alt="img"></p>
<p>带颜色的箭头代表了不同环境RPC消息流转。</p>
<h3 id="RPC改造设计"><a href="#RPC改造设计" class="headerlink" title="RPC改造设计"></a>RPC改造设计</h3><p>首先我们参考了 Google 的论文：Dapper，大规模分布式系统的跟踪系统。大部分 APM (Application Performance Management)都以此为基础开发。我们基于这个设计做了一些扩展来达成我们的需求。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333909.webp" alt="img"></p>
<p>在 APM 中有一个概念叫 TraceID, 它可以唯一标识一次调用。它本身只是唯一，没有其他含义。它可以在一个请求和它的子请求中保持。在我们的系统里，我们也需要类似的能力，除了需要 TraceID 以外，还需要一个 EnvID，当请求和它的子请求在不同的系统里传输的时候，除了会带上唯一的 TraceID, 还会带上 EnvID。我们依靠 EnvID 来实现多个环境的流转。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333903.webp" alt="img"></p>
<p>在上图箭头代表了 RPC 消息，不同的颜色代表这个消息里含有的 Env-ID 是不同的。比如红色箭头代表这个消息含有的 env-ID 是 FAT1，绿色的箭头代表含有的 env-ID 是 FAT2，蓝色箭头代表含有 Env-ID 是 Default。</p>
<p>在此时我们需要解决3个问题：</p>
<ol>
<li>EnvID 怎么生成？它具体什么样子</li>
<li>EnvID 在系统中如何传输？</li>
<li>EnvID 在什么时候工作，它具体工作的原理是怎么样的？它为什么能解决问题？</li>
</ol>
<p>为此我们用染色、透传、智能路由3个解决方案来解决这3个问题。下面就是具体的过程。</p>
<h3 id="问题1：EnvID-的生成，它具体什么样子？"><a href="#问题1：EnvID-的生成，它具体什么样子？" class="headerlink" title="问题1：EnvID 的生成，它具体什么样子？"></a>问题1：EnvID 的生成，它具体什么样子？</h3><p>目前所有的系统，都是靠客户端来发起的，比如 H5、手机、App 都是客户端，它们用标准的 Http 的请求和后端系统进行交互，发起业务。客户端使用类似这样的请求和服务器进行交互。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333781.webp" alt="img"></p>
<p>我们唯一需要做的，就是在流量进入各个环境的时候，需要加一个标识，表明自己的来源。类似这样</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333909.webp" alt="img"></p>
<p>我们用这个标识表明了消息来源于那个环境，这个过程我们称之认为是染色。它会在整个生命周期里一直存在，在它和它的子请求里不会变化。</p>
<p>染色方案的比较：关于如何染色有2个方案，一个我们称之为客户端染色，另一种为网关染色。</p>
<h4 id="客户端染色"><a href="#客户端染色" class="headerlink" title="客户端染色"></a>客户端染色</h4><p><img src="http://iubest.gitee.io/pic/640-1601008333908.webp" alt="img"></p>
<p>这种方案，需要改造客户端，让客户端发出的请求直接带上环境标识。这种方案的优点是标识可定制，操作性强。但缺点同样明显， 它会入侵前端业务，不灵活，H5、 Web等客户端可能不支持，如果环境标识的内容需要扩展，则所有客户端都需要重新改造。</p>
<h4 id="网关染色"><a href="#网关染色" class="headerlink" title="网关染色"></a>网关染色</h4><p><img src="http://iubest.gitee.io/pic/640-1601008333616.webp" alt="img"></p>
<p>网关染色的方案灵活很多，它不需要改造客户端，对业务没有入侵，对 H5、Web 也都能支持，升级也很方便。所以我们选择了网关染色的方案。</p>
<h3 id="问题2：EnvID-在系统中如何传输？"><a href="#问题2：EnvID-在系统中如何传输？" class="headerlink" title="问题2：EnvID 在系统中如何传输？"></a>问题2：EnvID 在系统中如何传输？</h3><p>这个其实是一个技术债了，其本质就是获得 APM 技术里类似 PinPoint、 zipkin、 jaeger、 skywalking 透传信息的能力。</p>
<p>结合我们项目的本身特点，如果选用有代码入侵的方案，大量现有系统改造成本太高。只能采用无代码入侵的方案。最后我们实现了一个J ava Agent 实现了类似 Skywalking/pip 的能力。将 Env-ID 能够通过应用，在子请求中继续传播下去。</p>
<p>在下面的例子可以看到 应用1 在收到一个请求后，它的子请求上都带上了一样的Env-id</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333694.webp" alt="img"></p>
<h3 id="问题3：EnvID-在什么时候工作，它具体工作的原理是怎么样的？"><a href="#问题3：EnvID-在什么时候工作，它具体工作的原理是怎么样的？" class="headerlink" title="问题3：EnvID 在什么时候工作，它具体工作的原理是怎么样的？"></a>问题3：EnvID 在什么时候工作，它具体工作的原理是怎么样的？</h3><p>虽然我们已经知道了 EnvID，如何产生，如何透传，如何传播，但是我们仍然搞不清楚，它怎么帮助我们完成多个环境应用复用呢？那么下面我们将详细解释EnvID 是如何工作的，我们称这个过程为<em>智能路由</em>。</p>
<p>这个过程可以分为2种情况。在这里我们演示了 A 应用调用 B 应用，B 应用调用 C 应用，这样一次调用过程。</p>
<h4 id="普通情况"><a href="#普通情况" class="headerlink" title="普通情况"></a>普通情况</h4><p><img src="http://iubest.gitee.io/pic/640-1601008333903.webp" alt="img"></p>
<p>这种情况很简单，这里的情况说明每个环境里都有不同的版本，应用之间调用不会跨环境，每个应用都只调用本环境内的其他应用。</p>
<p>真实的运行情况，其实是这样的。每次请求都是通过网关转发一下的，并没有直接连接的。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333909.webp" alt="img"></p>
<p>星云多环境平台控制了 Dns，也控制网关转发规则，星云多环境平台系统让所有应用将请求都发给网关，网关拿到请求，根据规则再转发出去。在这种情况下，似乎没有太大意义，直连也可以。好我们看下一种场景。</p>
<h4 id="特殊情况"><a href="#特殊情况" class="headerlink" title="特殊情况"></a>特殊情况</h4><p>在这种场景下 我们将实现下面的效果</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333840.webp" alt="img"></p>
<p>这里基本涵盖了所有的应用复用的所有情况。</p>
<p>在测试环境 FAT1 里，A 应用的版本1实例希望将请求发往 B 应用，但是 FAT1 环境里没有 B 应用的实例，所以请求被发往了 Default 环境的 B 应用的稳定版本实例，在 Default 环境的 B 应用的稳定版本实例处理后，B 应用的稳定版本实例希望将请求发往 C 应用，因为 FAT1 环境里也没有 C 应用，所以请求继续发往了 Default 环境的 C 应用的稳定版本。</p>
<p>在测试环境 FAT2 里，A 应用的版本2实例希望将请求发往 B 应用，但是 FAT2环境也没有 B 应用的实例，所以请求被发往了 Default 环境的 B 应用的稳定版本实例，在 Default 环境的 B 应用的稳定版本实例处理后，B 应用的稳定版本实例希望将请求发往 C 应用，因为 FAT-2 环境有 C 应用的实例. 所以 Default 环境的 B 应用的稳定版本实例将请求发往了测试环境 Fat2 里的 C 版本2。最终实现了消息在不同环境内的穿梭，也实现了我们先要的应用复用。</p>
<p>在 Default 环境里的消息 则和上面一样。究竟这是如何实现的呢？真实的流程大概如下。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333908.webp" alt="img"></p>
<p>每个环境的网关的规则是星云系统自动生成的，星云系统知道所有环境实例的状态，所以星云是这样写网关规则的，如果一个环境里有某个应用的实例，那么网关就把请求指向本环境实例，如果没有此实例，则把这个应用的目标指向 Default的网关。</p>
<p>这样在本例中，当测试环境的 FAT1，FAT2 网关收到一个发往B应用的请求时，因为本环境里没有B 应用，那么请求就直接被发往了 Default 网关。</p>
<p>Default 网关又是如何工作的呢？Default 网关是一台 Openresty 服务器，我们略微扩展了一下它的能力，它的工作流程如下：</p>
<ol>
<li>它收到请求消息后，先解析请求，先找出Env-ID和目标域名，确定它是来自哪个环境的，它想去那个目标应用。</li>
<li>它会利用目标应用域名和Env-ID 去星云里查找这个实例信息.</li>
<li>如果星云告诉它 域名+Env-ID有实例存在，则把消息转发给 Env-ID环境的网关，如果域名+Env-ID实例不存在，就把消息转发给Default环境里对应的实例。</li>
</ol>
<p>按照上面的规则，我们把上面的实现再看一遍。以测试环境 FAT2 为例：</p>
<ol>
<li>A应用的版本2，将请求发往B应用，请求首先被发往 FAT2 的网关</li>
<li>Fat2 的网关收到消息，进行转发，因为 FAT2 环境里没有B应用的实例，按照预先设计，FAT2 环境的网关把请求转发到了 Default 网关</li>
<li>Default 网关收到了这个消息，解析出 <code>Env-ID：FAT2</code> 和目标域名 B。</li>
<li>Default 网关向星云查询：Env-ID 为 FAT2 域名为B的实例是否存在？星云答复没有实例</li>
<li>Default 网关把实例发给了 Default 环境的B应用的稳定版本实例</li>
<li>B应用的稳定版本实例处理完以后，继续请求C应用，B应用的稳定版本实例将请求发往 Default 网关</li>
<li>Default 网关收到请求，解析出 <code>Env-ID: Fat2</code> 和目标域名C</li>
<li>Default 网关向星云系统查询：Env-ID 为 FAT2 域名为C的实例是否存在？星云答复有实例</li>
<li>Default 网关将请求转发至 FAT2 环境的网关</li>
<li>Fat2 环境的网关收到请求，将消息转发至C应用的版本2实例上。</li>
</ol>
<p>按照这个设计方案，我们在不改动应用的前提下实现了我们的目标。按照这套设计无论环境里有还是没有对应实例，消息都能按照我们设计的流程进行流转。这样可以帮助我们节省大量的实例。</p>
<h4 id="RPC-改造成本"><a href="#RPC-改造成本" class="headerlink" title="RPC 改造成本"></a>RPC 改造成本</h4><p>在项目启动之前，我也参考过很多公司经验，我认为其中有一项比较挑战的地方就是改造成本。不少大公司在 RPC 设计之初就已经考虑了染色和按标签路由的功能，他们在所有的应用在开发是已经注意了这个问题。而对于我们这个项目而言，存在大量历史系统，不具备这个条件，如果改造 RPC，我们需要把所有的项目都重新改造，打包，测试，时间成本和人力成本都太高。我们采用了目前这种成本较低的方案，把应用改造作为另一项长期方案来完成。最终做到快速、低成本上线。</p>
<p>如果在 RPC 这些方面基础措施做得好，那么实现这个目标会简单。此外还有入侵性较小的方案是采用 Service Mesh 的方式来实现，将网关的功能在 Sidecar 里实现, 但是消息透传仍然是需要解决的。</p>
<h2 id="Redis优化"><a href="#Redis优化" class="headerlink" title="Redis优化"></a>Redis优化</h2><p>除了 RPC 以外， Redis 也是我们需要攻克的技术点。因为拉起了新环境，为避免数据冲突，是需要新的 Redis 实例。按照常规的做法，需要为各个环境里重新生成 Redis 实例。但是我们遇到了一些问题，因为 Redis 的使用方法很多，有的项目使用 IP+端口，有的采用域名，有的使用 Cachecloud，并不统一。长期地看规范使用 redis 是一个解决办法，但是如何解决现有问题呢？常见的办法是需要进行替换配置，这不可避免的会入侵到业务的配置。为减少这种入侵，我们采用了 Java Agent 的模式，拦截了应用调用 Redis 的接口，在 Redis 的 key 前面加了一个前缀，这种既做到了数据隔离，又做到节省资源。大概的原理这样</p>
<p><img src="http://iubest.gitee.io/pic/640-1601008333841.webp" alt="img"></p>
<p>不同环境所有数据都被加上了一个前缀，做到了相互不干扰，这样一个 redis 实例就可以同时被多个环境共享了。（如果开发初期，规范里就预制了 redis 数据前缀配置的话，解决这个问题就会简单。）</p>
<h2 id="前端优化"><a href="#前端优化" class="headerlink" title="前端优化"></a>前端优化</h2><p>除了后端以外，我们的前端也都上到容器里，但是前端使用容器存在一个资源问题，大部分的前端站点都是编译后的静态文件，文件不大，资源消耗也很低，但是域名数量非常多，一套环境里经常有40+前端站点。按普通方案，我们需要启动40多个容器来完成。这样资源利用率较低，为了节省资源，我们将一个项目的全部静态资源最终打到一个镜像里。然后利用 dns 和网关配合，实现前端站点的代理，做到了一个应用同时服务几十个静态站点，不但资源节省了，站点管理起来也非常简单，灵活。</p>
<h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><p>目前所有的环境都复用了一套数据库系统，究其原因还是因为数据库的规模太大，数据初始化，复用，数据合并上还有比较大的挑战。所以用户测试一般使用不同的区域、账号进行逻辑隔离，目前来看也是满足需要的。但是数据整理仍有很大的进步空间。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>依靠这些技术实施，我们创建了这套系统，可以在10分钟级别交付一个含有近百实例的测试环境，目前公司已经有上百套环境在同时运行，极大减轻了测试环境紧缺的问题。利用 K8S，我们灵活控制了所有的环境，这表明基于 K8S 的应用管理系统可以极大地提高研发效率，容器技术和容器编排这方面还有很大的潜力可以挖掘。愿我们的经验能给大家有所启迪。如有问题可以邮件（<a href="mailto:&#115;&#x68;&#x65;&#x6e;&#106;&#x69;&#x6f;&#x6e;&#103;&#x40;&#120;&#x69;&#x6e;&#121;&#x65;&#46;&#x63;&#x6f;&#x6d;">&#115;&#x68;&#x65;&#x6e;&#106;&#x69;&#x6f;&#x6e;&#103;&#x40;&#120;&#x69;&#x6e;&#121;&#x65;&#46;&#x63;&#x6f;&#x6d;</a> 或 <a href="mailto:&#112;&#112;&#x6d;&#115;&#x6e;&#50;&#x30;&#48;&#x35;&#x40;&#x67;&#x6d;&#97;&#105;&#108;&#x2e;&#x63;&#x6f;&#x6d;">&#112;&#112;&#x6d;&#115;&#x6e;&#50;&#x30;&#48;&#x35;&#x40;&#x67;&#x6d;&#97;&#105;&#108;&#x2e;&#x63;&#x6f;&#x6d;</a>）进行讨论。</p>
<h2 id="热门文章推荐"><a href="#热门文章推荐" class="headerlink" title="热门文章推荐"></a>热门文章推荐</h2><ul>
<li><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA4MzIwNTc4NQ==&mid=2247486673&idx=1&sn=88ff09cb51a31d959163536708bf3f4f&chksm=9ffb4727a88cce311550c9f2eb1dcd7d52b9c337bbca04eae17aa6fe6ba6e46054620c6ba687&scene=21#wechat_redirect">免费下载：280 页 InfoQ《2020 中国技术发展白皮书》</a></li>
<li><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA4MzIwNTc4NQ==&mid=2247486665&idx=1&sn=a53576ad1c9930587e3a6c6534d677b8&chksm=9ffb473fa88cce29f5982fee064c97d663a4f593339f02c0bcd42d55516ea27ef14be8bf107c&scene=21#wechat_redirect">Kubernetes YAML 生成器</a></li>
<li><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA4MzIwNTc4NQ==&mid=2247486650&idx=1&sn=75259287bb54170412d116c329afc36f&chksm=9ffb474ca88cce5a19003594f8025b04a586f92a5c1cad473072d2014c6d6891fdbd25d579c1&scene=21#wechat_redirect">Kubernetes 故障解决心得（一）</a></li>
<li><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA4MzIwNTc4NQ==&mid=2247486614&idx=1&sn=1518def129c34507919af4e02d7dcbae&chksm=9ffb4760a88cce764f5b98c20b86f5db08bfad330ec3461598f5bda96983aa5fe48ca3c2cd82&scene=21#wechat_redirect">Kubernetes 临时存储需要限制吗？</a></li>
<li><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA4MzIwNTc4NQ==&mid=2247486454&idx=1&sn=5563754522286abebf9cd943dbef88be&chksm=9ffb4000a88cc9168bcb8725b678c5716346ae623ca12309f85728449d4380d72ef4d85d7f46&scene=21#wechat_redirect">Linux Used内存到底哪里去了？</a></li>
<li><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA4MzIwNTc4NQ==&mid=2247485336&idx=1&sn=878da042321977284aea77897d647fe5&chksm=9ffb4c6ea88cc57871460a57443c9ae886138e5aea3bc7cc33ce8806ee6f0ade12d3b5819af4&scene=21#wechat_redirect">K8S备份、恢复、迁移神器 Velero</a></li>
</ul>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/k8s/" rel="tag">k8s</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-linux/Linux服务器高并发调优实战"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/linux/Linux%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%AB%98%E5%B9%B6%E5%8F%91%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/"
    >Linux服务器高并发调优实战.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/linux/Linux%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%AB%98%E5%B9%B6%E5%8F%91%E8%B0%83%E4%BC%98%E5%AE%9E%E6%88%98/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/linux/">linux</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="Linux服务器高并发调优实战"><a href="#Linux服务器高并发调优实战" class="headerlink" title="Linux服务器高并发调优实战"></a>Linux服务器高并发调优实战</h2><p><strong>高并发情况下Linux系统及kernel参数优化</strong></p>
<p>众所周知在默认参数情况下Linux对高并发支持并不好，主要受限于单进程最大打开文件数限制、内核TCP参数方面和IO事件分配机制等。下面就从几方面来调整使Linux系统能够支持高并发环境。</p>
<h3 id="iptables相关"><a href="#iptables相关" class="headerlink" title="iptables相关"></a>iptables相关</h3><p>如非必须，关掉或卸载iptables防火墙，并阻止kernel加载iptables模块。这些模块会影响并发性能。</p>
<h4 id="单进程最大打开文件数限制"><a href="#单进程最大打开文件数限制" class="headerlink" title="单进程最大打开文件数限制"></a>单进程最大打开文件数限制</h4><p>一般的发行版，限制单进程最大可以打开1024个文件，这是远远不能满足高并发需求的，调整过程如下：</p>
<p>在#号提示符下敲入：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># ulimit –n 65535</span><br></pre></td></tr></table></figure>

<p>将root启动的单一进程的最大可以打开的文件数设置为65535个。如果系统回显类似于“Operationnotpermitted”之类的话，说明上述限制修改失败，实际上是因为在中指定的数值超过了Linux系统对该用户打开文件数的软限制或硬限制。因此，就需要修改Linux系统对用户的关于打开文件数的软限制和硬限制。</p>
<p>第一步，修改limits.conf文件，并添加：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># vim &#x2F;etc&#x2F;security&#x2F;limits.conf</span><br><span class="line">* soft nofile 65535</span><br><span class="line">* hard nofile 65535</span><br></pre></td></tr></table></figure>

<p>其中’*’号表示修改所有用户的限制；soft或hard指定要修改软限制还是硬限制；65536则指定了想要修改的新的限制值，即最大打开文件数(请注意软限制值要小于或等于硬限制)。修改完后保存文件。</p>
<p>第二步，修改/etc/pam.d/login文件，在文件中添加如下行：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># vim &#x2F;etc&#x2F;pam.d&#x2F;login</span><br><span class="line">sessionrequired &#x2F;lib&#x2F;security&#x2F;pam_limits.so</span><br></pre></td></tr></table></figure>

<p>这是告诉Linux在用户完成系统登录后，应该调用pam_limits.so模块来设置系统对该用户可使用的各种资源数量的最大限制(包括用户可打开的最大文件数限制)，而pam_limits.so模块就会从/etc/security/limits.conf文件中读取配置来设置这些限制值。修改完后保存此文件。</p>
<p>第三步，查看Linux系统级的最大打开文件数限制，使用如下命令：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># cat&#x2F;proc&#x2F;sys&#x2F;fs&#x2F;file-max</span><br><span class="line">32568</span><br></pre></td></tr></table></figure>

<p>这表明这台Linux系统最多允许同时打开(即包含所有用户打开文件数总和)32568个文件，是Linux系统级硬限制，所有用户级的打开文件数限制都不应超过这个数值。通常这个系统级硬限制是Linux系统在启动时根据系统硬件资源状况计算出来的最佳的最大同时打开文件数限制，如果没有特殊需要，不应该修改此限制，除非想为用户级打开文件数限制设置超过此限制的值。修改此硬限制的方法是修改/etc/sysctl.conf文件内fs.file-max= 131072</p>
<p>这是让Linux在启动完成后强行将系统级打开文件数硬限制设置为131072。修改完后保存此文件。</p>
<p>完成上述步骤后重启系统，一般情况下就可以将Linux系统对指定用户的单一进程允许同时打开的最大文件数限制设为指定的数值。如果重启后用ulimit-n命令查看用户可打开文件数限制仍然低于上述步骤中设置的最大值，这可能是因为在用户登录脚本/etc/profile中使用ulimit-n命令已经将用户可同时打开的文件数做了限制。由于通过ulimit-n修改系统对用户可同时打开文件的最大数限制时，新修改的值只能小于或等于上次ulimit-n设置的值，因此想用此命令增大这个限制值是不可能的。所以，如果有上述问题存在，就只能去打开/etc/profile脚本文件，在文件中查找是否使用了ulimit-n限制了用户可同时打开的最大文件数量，如果找到，则删除这行命令，或者将其设置的值改为合适的值，然后保存文件，用户退出并重新登录系统即可。</p>
<p>通过上述步骤，就为支持高并发TCP连接处理的通讯处理程序解除关于打开文件数量方面的系统限制。</p>
<h4 id="内核TCP参数方面"><a href="#内核TCP参数方面" class="headerlink" title="内核TCP参数方面"></a>内核TCP参数方面</h4><p>Linux系统下，TCP连接断开后，会以TIME_WAIT状态保留一定的时间，然后才会释放端口。当并发请求过多的时候，就会产生大量的TIME_WAIT状态的连接，无法及时断开的话，会占用大量的端口资源和服务器资源。这个时候我们可以优化TCP的内核参数，来及时将TIME_WAIT状态的端口清理掉。</p>
<p>下面介绍的方法只对拥有大量TIME_WAIT状态的连接导致系统资源消耗有效，如果不是这种情况下，效果可能不明显。可以使用netstat命令去查TIME_WAIT状态的连接状态，输入下面的组合命令，查看当前TCP连接的状态和对应的连接数量：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># netstat-n | awk ‘&#x2F;^tcp&#x2F; &#123;++S[$NF]&#125; END &#123;for(a in S) print a, S[a]&#125;’</span><br><span class="line">这个命令会输出类似下面的结果：</span><br></pre></td></tr></table></figure>



<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LAST_ACK16SYN_RECV348ESTABLISHED70FIN_WAIT1229FIN_WAIT230CLOSING33TIME_WAIT18098</span><br></pre></td></tr></table></figure>

<p>我们只用关心TIME_WAIT的个数，在这里可以看到，有18000多个TIME_WAIT，这样就占用了18000多个端口。要知道端口的数量只有65535个，占用一个少一个，会严重的影响到后继的新连接。这种情况下，我们就有必要调整下Linux的TCP内核参数，让系统更快的释放TIME_WAIT连接。</p>
<p>编辑配置文件:/etc/sysctl.conf，在这个文件中，加入下面的几行内容：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># vim &#x2F;etc&#x2F;sysctl.confnet.ipv4.tcp_syncookies&#x3D; 1net.ipv4.tcp_tw_reuse&#x3D; 1net.ipv4.tcp_tw_recycle&#x3D; 1net.ipv4.tcp_fin_timeout&#x3D; 30</span><br></pre></td></tr></table></figure>

<p>输入下面的命令，让内核参数生效：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># sysctl-p</span><br></pre></td></tr></table></figure>

<p>简单的说明上面的参数的含义：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_syncookies&#x3D; 1</span><br></pre></td></tr></table></figure>

<p>表示开启SYNCookies。当出现SYN等待队列溢出时，启用cookies来处理，可防范少量SYN攻击，默认为0，表示关闭；</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_tw_reuse&#x3D; 1</span><br></pre></td></tr></table></figure>

<p>表示开启重用。允许将TIME-WAITsockets重新用于新的TCP连接，默认为0，表示关闭；</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_tw_recycle&#x3D; 1</span><br></pre></td></tr></table></figure>

<p>表示开启TCP连接中TIME-WAITsockets的快速回收，默认为0，表示关闭；</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_fin_timeout</span><br></pre></td></tr></table></figure>

<p>修改系統默认的TIMEOUT 时间。</p>
<p>在经过这样的调整之后，除了会进一步提升服务器的负载能力之外，还能够防御小流量程度的DoS、CC和SYN攻击。</p>
<p>此外，如果你的连接数本身就很多，我们可以再优化一下TCP的可使用端口范围，进一步提升服务器的并发能力。依然是往上面的参数文件中，加入下面这些配置：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_keepalive_time&#x3D; 1200net.ipv4.ip_local_port_range&#x3D; 1024 65535net.ipv4.tcp_max_syn_backlog&#x3D; 8192net.ipv4.tcp_max_tw_buckets&#x3D; 5000</span><br></pre></td></tr></table></figure>

<p>这几个参数，建议只在流量非常大的服务器上开启，会有显著的效果。一般的流量小的服务器上，没有必要去设置这几个参数。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_keepalive_time&#x3D; 1200</span><br></pre></td></tr></table></figure>

<p>表示当keepalive起用的时候，TCP发送keepalive消息的频度。缺省是2小时，改为20分钟。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip_local_port_range&#x3D; 1024 65535</span><br></pre></td></tr></table></figure>

<p>表示用于向外连接的端口范围。缺省情况下很小，改为1024到65535。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_max_syn_backlog&#x3D; 8192</span><br></pre></td></tr></table></figure>

<p>表示SYN队列的长度，默认为1024，加大队列长度为8192，可以容纳更多等待连接的网络连接数。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_max_tw_buckets&#x3D; 5000</span><br></pre></td></tr></table></figure>

<p>表示系统同时保持TIME_WAIT的最大数量，如果超过这个数字，TIME_WAIT将立刻被清除并打印警告信息。默认为180000，改为5000。此项参数可以控制TIME_WAIT的最大数量，只要超出了。</p>
<p>内核其他TCP参数说明</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_max_syn_backlog&#x3D; 65535</span><br></pre></td></tr></table></figure>

<p>记录的那些尚未收到客户端确认信息的连接请求的最大值。对于有128M内存的系统而言，缺省值是1024，小内存的系统则是128。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.core.netdev_max_backlog&#x3D; 32768</span><br></pre></td></tr></table></figure>

<p>每个网络接口接收数据包的速率比内核处理这些包的速率快时，允许送到队列的数据包的最大数目。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.core.somaxconn&#x3D; 32768</span><br></pre></td></tr></table></figure>

<p>例如web应用中listen函数的backlog默认会给我们内核参数的net.core.somaxconn限制到128，而nginx定义的NGX_LISTEN_BACKLOG默认为511，所以有必要调整这个值。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.core.wmem_default&#x3D; 8388608net.core.rmem_default&#x3D; 8388608net.core.rmem_max&#x3D; 16777216 #最大socket读buffer,可参考的优化值:873200net.core.wmem_max&#x3D; 16777216 #最大socket写buffer,可参考的优化值:873200net.ipv4.tcp_timestsmps&#x3D; 0</span><br></pre></td></tr></table></figure>

<p>时间戳可以避免序列号的卷绕。一个1Gbps的链路肯定会遇到以前用过的序列号。时间戳能够让内核接受这种“异常”的数据包。这里需要将其关掉。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_synack_retries&#x3D; 2</span><br></pre></td></tr></table></figure>

<p>为了打开对端的连接，内核需要发送一个SYN并附带一个回应前面一个SYN的ACK。也就是所谓三次握手中的第二次握手。这个设置决定了内核放弃连接之前发送SYN+ACK包的数量。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_syn_retries&#x3D; 2</span><br></pre></td></tr></table></figure>

<p>在内核放弃建立连接之前发送SYN包的数量。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#net.ipv4.tcp_tw_len&#x3D; 1net.ipv4.tcp_tw_reuse&#x3D; 1</span><br></pre></td></tr></table></figure>

<p>开启重用。允许将TIME-WAITsockets重新用于新的TCP连接。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_wmem&#x3D; 8192 436600 873200TCP写buffer,可参考的优化值:8192 436600 873200net.ipv4.tcp_rmem &#x3D; 32768 436600 873200TCP读buffer,可参考的优化值:32768 436600 873200net.ipv4.tcp_mem&#x3D; 94500000 91500000 92700000同样有3个值,意思是:net.ipv4.tcp_mem[0]:低于此值，TCP没有内存压力。net.ipv4.tcp_mem[1]:在此值下，进入内存压力阶段。net.ipv4.tcp_mem[2]:高于此值，TCP拒绝分配socket。上述内存单位是页，而不是字节。可参考的优化值是:7864321048576 1572864net.ipv4.tcp_max_orphans&#x3D; 3276800</span><br></pre></td></tr></table></figure>

<p>系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。</p>
<p>如果超过这个数字，连接将即刻被复位并打印出警告信息。</p>
<p>这个限制仅仅是为了防止简单的DoS攻击，不能过分依靠它或者人为地减小这个值，</p>
<p>更应该增加这个值(如果增加了内存之后)。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_fin_timeout&#x3D; 30</span><br></pre></td></tr></table></figure>

<p>如果套接字由本端要求关闭，这个参数决定了它保持在FIN-WAIT-2状态的时间。对端可以出错并永远不关闭连接，甚至意外当机。缺省值是60秒。2.2 内核的通常值是180秒，你可以按这个设置，但要记住的是，即使你的机器是一个轻载的WEB服务器，也有因为大量的死套接字而内存溢出的风险，FIN-WAIT-2的危险性比FIN-WAIT-1要小，因为它最多只能吃掉1.5K内存，但是它们的生存期长些。</p>
<p>同时还涉及到一个TCP 拥塞算法的问题，你可以用下面的命令查看本机提供的拥塞算法控制模块：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sysctlnet.ipv4.tcp_available_congestion_control</span><br><span class="line">对于几种算法的分析，详情可以参考下：TCP拥塞控制算法的优缺点、适用环境、性能分析，比如高延时可以试用hybla，中等延时可以试用htcp算法等。</span><br></pre></td></tr></table></figure>

<p>如果想设置TCP 拥塞算法为hybla</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_congestion_control&#x3D;hybla</span><br><span class="line">额外的，对于内核版高于于3.7.1的，我们可以开启tcp_fastopen：</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.tcp_fastopen&#x3D; 3</span><br></pre></td></tr></table></figure>

<h4 id="IO事件分配机制"><a href="#IO事件分配机制" class="headerlink" title="IO事件分配机制"></a>IO事件分配机制</h4><p>在Linux启用高并发TCP连接，必须确认应用程序是否使用了合适的网络I/O技术和I/O事件分派机制。可用的I/O技术有同步I/O，非阻塞式同步I/O，以及异步I/O。在高TCP并发的情形下，如果使用同步I/O，这会严重阻塞程序的运转，除非为每个TCP连接的I/O创建一个线程。但是，过多的线程又会因系统对线程的调度造成巨大开销。因此，在高TCP并发的情形下使用同步I/O是不可取的，这时可以考虑使用非阻塞式同步I/O或异步I/O。非阻塞式同步I/O的技术包括使用select()，poll()，epoll等机制。异步I/O的技术就是使用AIO。</p>
<p>从I/O事件分派机制来看，使用select()是不合适的，因为它所支持的并发连接数有限(通常在1024个以内)。如果考虑性能，poll()也是不合适的，尽管它可以支持的较高的TCP并发数，但是由于其采用“轮询”机制，当并发数较高时，其运行效率相当低，并可能存在I/O事件分派不均，导致部分TCP连接上的I/O出现“饥饿”现象。而如果使用epoll或AIO，则没有上述问题(早期Linux内核的AIO技术实现是通过在内核中为每个I/O请求创建一个线程来实现的，这种实现机制在高并发TCP连接的情形下使用其实也有严重的性能问题。但在最新的Linux内核中，AIO的实现已经得到改进)。</p>
<p>综上所述，在开发支持高并发TCP连接的Linux应用程序时，应尽量使用epoll或AIO技术来实现并发的TCP连接上的I/O控制，这将为提升程序对高并发TCP连接的支持提供有效的I/O保证。</p>
<p>经过这样的优化配置之后，服务器的TCP并发处理能力会显著提高。以上配置仅供参考，用于生产环境请根据自己的实际情况调整观察再调整。</p>
<p>扫码关注</p>
<p>绝对干货的公众号</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001376240.gif" alt="img"></p>
<p><img src="http://iubest.gitee.io/pic/640-1601001369023.webp" alt="img"></p>
<p><img src="http://iubest.gitee.io/pic/640-1601001368966.gif" alt="img"></p>
<p>推荐阅读</p>
<p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484733&idx=1&sn=8a5fcad29d0fea680f8ba93fea44c0da&chksm=fe7b0a01c90c8317e174ca316b1c15dd11bdfe41f1c70b3331576683ca87a0d2e8de267b99ad&token=1047100134&lang=zh_CN&scene=21#wechat_redirect">
</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484673&idx=1&sn=f1c6fd7c00049734ed97121b5c401c6a&chksm=fe7b0a3dc90c832b226d1df991f413a4dfaebce8c4be4549ebd777a688e08f61f651b1bcd337&scene=21#wechat_redirect">实战项目–kkitDeploy（客户端版）(强推,已开源)</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485054&idx=1&sn=76b2afbaa98227a0efb06e1c5e53be17&chksm=fe7b0942c90c8054172c8ca3020ddeb28d32cee3d3807efc6e7c9b063d128ee31dcdc831f457&scene=21#wechat_redirect">实战项目–kkitDeploy（PaaS版）(强推)</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484871&idx=1&sn=eea93b8ec270325f961e41038eade6d4&chksm=fe7b0afbc90c83ed0251453b69c1ac0e48db7444dd27caf8d8830b03235190cb7802058c64b6&scene=21#wechat_redirect">实战项目–kkit3.0功能介绍(强推)</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247483880&idx=1&sn=5a38522f8639330a62fd8bd1fe032972&chksm=fe7b0ed4c90c87c26e4444cf21254dc613258082b28b4a6707a041edadb982ab0d13b994749a&scene=21#wechat_redirect">实战项目–kkit1.0功能介绍(强推,已开源)</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484768&idx=1&sn=4016a88bc54e9fe67b7b6d76fa3cedb4&chksm=fe7b0a5cc90c834ae4e95f951324ffe2a9991b502da194c507c6d02b7b277403f00fcc123940&scene=21#wechat_redirect">面试题之—网络安全（2019年10月更新）</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484733&idx=1&sn=8a5fcad29d0fea680f8ba93fea44c0da&chksm=fe7b0a01c90c8317e174ca316b1c15dd11bdfe41f1c70b3331576683ca87a0d2e8de267b99ad&scene=21#wechat_redirect">面试题之—k8s（2019年10月更新）</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484360&idx=1&sn=b0f6bdca5a3f4f4ac3c53c9f1ef9cbc9&chksm=fe7b0cf4c90c85e2ee1afb0571603426ab172c5c2e1d85d4763b086a562f5673973d4230e17a&scene=21#wechat_redirect">面试题之—运维工程师经典面试汇总（一）</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484747&idx=1&sn=b49dfe531c0c1331200a7e4947f65976&chksm=fe7b0a77c90c8361f2ac4f3784681c7580c3699bf0f49d930b56999a2bd8e3e62fa3e6c9b521&scene=21#wechat_redirect">经历贴之—外包接活却坐牢456天完整记录（强推）</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484455&idx=1&sn=da41f73724909d3051343133cd53db74&chksm=fe7b0b1bc90c820d954f649d76be230f651d62431858eb06bae8f5deaa36e66339f515116373&scene=21#wechat_redirect">脚本之—短信轰炸机</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484740&idx=1&sn=0683fbf63643218045c1b8cd522137ec&chksm=fe7b0a78c90c836e85c254cce4b8c6897579fd4f6a1386b59084b6fe4cfdd3a72bbcbfbce96f&scene=21#wechat_redirect">脚本之—QQ微信轰炸机</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484909&idx=1&sn=27ab3125be7e9fd31e93fa1cb30bd3ae&chksm=fe7b0ad1c90c83c7158a5679dec8d987a1f05f933efd4073d15ef83a98e897666ed88c235e2f&scene=21#wechat_redirect">脚本之—linux流量监控利器</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484957&idx=1&sn=ad4f9320dfd49a0c7a54042a20bb66c0&chksm=fe7b0921c90c8037a273419352a5677461e9895ca945bd6e33fae2fc401443c6c7a34ef355d9&scene=21#wechat_redirect">脚本之—linux系统巡检脚本合集</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485039&idx=1&sn=ddd14e33325dfd99ac61ee83a4fc198c&chksm=fe7b0953c90c8045e6de3e39cc83b53e067d1a19a051dd17129e8ec2386b36adc9c4d6d1589d&scene=21#wechat_redirect">脚本之—linux批量用户管理脚本</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485046&idx=1&sn=ee3e7a258eafcce88aa65e3daa3fd7be&chksm=fe7b094ac90c805c6ed4e8eb16a698fee1e1b467e948b0e86288aa39cbfebd48ee5e383594c1&scene=21#wechat_redirect">脚本之—根据PID查看相关信息</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485065&idx=1&sn=8913154de8cb68c4679d5a40957eaf18&chksm=fe7b09b5c90c80a323324d176c0be3a3370f1ee3fc9d05625937b7d8c0df206c2e23b6c4773a&scene=21#wechat_redirect">脚本之—一键完成系统安全加固</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485059&idx=1&sn=f4671cd621367c9faf5d697b181e351a&chksm=fe7b09bfc90c80a9d89b23553d1366d17686dd5e3542a17df738d9ead2274fb104943237bc5b&scene=21#wechat_redirect">脚本之—检查系统用户</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485014&idx=1&sn=a633abf6ec6137240528957a16de2f33&chksm=fe7b096ac90c807c09b7881315263c7d6cf87f0fa038d4159b00c2518e5bce619fe2bf68faea&scene=21#wechat_redirect">脚本之—一键同步脚本文件夹</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485111&idx=1&sn=6332d3366f992980cd16862e9974b3c0&chksm=fe7b098bc90c809d65219f1ffe51a03c68c76615bbef5beed46ec59bbea805e1e5d1eb8d8409&scene=21#wechat_redirect">脚本之—一键升/降级k8s集群脚本</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485122&idx=1&sn=d88e6ae5a5327b783c9c7e26fe4615ac&chksm=fe7b09fec90c80e8c61c6e76b6ac514457e9872871a7bb0b6f285297b676307b99bde99278bb&scene=21#wechat_redirect">脚本之—一键升新增k8s集群node节点脚本</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485128&idx=1&sn=3adf091282012695ac2b7d6aadfe00c3&chksm=fe7b09f4c90c80e287710850a2d4912cdea3484537b511c1451a30cd5767d1e7b1af90bbd660&scene=21#wechat_redirect">脚本之—k8s一键部署脚本（多版本通用版）（强推）</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485159&idx=1&sn=a0516b2fb0197e8bd603cbb225d96cf1&chksm=fe7b09dbc90c80cd9fcebdf2285b48d50fd6bdda812565769b413da6b806411e5d634c4ba119&scene=21#wechat_redirect">脚本之—一键部署ceph（luminous）集群脚本</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485197&idx=1&sn=5225f0b73fd496332db24f5cded6c87f&chksm=fe7b0831c90c8127d737569822b00319193fb733a452f65c1e5eb936bc08aa914dcdf584850d&scene=21#wechat_redirect">脚本之—一键部署openvpn脚本</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485203&idx=1&sn=7a4d5359527a6b8be6e7840e2db7fe25&chksm=fe7b082fc90c813968dd6c244bb86b72deb8d68f81b02b7578dfe98db5deb5517ff7d755c747&scene=21#wechat_redirect">脚本之—一键（单双向）互信初始化服务器脚本</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485388&idx=1&sn=d12267364d1066de084b3a02e55da1f0&chksm=fe7b08f0c90c81e6044cea65f30026207dd4bf95098cdb01caf77f371038da8bdcc876b39ce5&scene=21#wechat_redirect">脚本之—一键初始化调优服务器脚本</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247484965&idx=1&sn=50455d6e48c8e58f98a1b2b9ea3c0ae1&chksm=fe7b0919c90c800f7a49d154072edc20a5cee3aca7dbd4e1e292b35664417d0acabfab7e58ff&scene=21#wechat_redirect">脚本之—zabbixAgent一键部署脚本（windos/linux）</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247485248&idx=1&sn=1134c6914570f1b5ad657cb0864d14d1&chksm=fe7b087cc90c816a7b172c552061370bc6db54679af785dbc899580a66d1e4b227c783b66409&scene=21#wechat_redirect">ansible—一键搭建redis5.0.5集群</a></p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzU5NDg5MzM5NQ==&mid=2247483959&idx=1&sn=f1c132a3f3ef3c2cf53166d92bbb1c81&chksm=fe7b0d0bc90c841d865c6ada6ad1b1747636b2ca719906ce5faf36a4775b595fb83cdcbd7988&scene=21#wechat_redirect">工具之—地表最强CMDB自动抓取工具</a></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/linux/" rel="tag">linux</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-linux/Shell 脚本编程最佳实践"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/linux/Shell%20%E8%84%9A%E6%9C%AC%E7%BC%96%E7%A8%8B%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"
    >Shell 脚本编程最佳实践.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/linux/Shell%20%E8%84%9A%E6%9C%AC%E7%BC%96%E7%A8%8B%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/linux/">linux</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Shell-脚本编程最佳实践"><a href="#Shell-脚本编程最佳实践" class="headerlink" title="Shell 脚本编程最佳实践"></a>Shell 脚本编程最佳实践</h1><h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>由于工作需要，最近重新开始拾掇shell脚本。虽然绝大部分命令自己平时也经常使用，但是在写成脚本的时候总觉得写的很难看。而且当我在看其他人写的脚本的时候，总觉得难以阅读。毕竟shell脚本这个东西不算是正经的编程语言，他更像是一个工具，用来杂糅不同的程序供我们调用。</p>
<p>因此很多人在写的时候也是想到哪里写到哪里，基本上都像是一段超长的main函数，不忍直视。同时，由于历史原因，shell有很多不同的版本，而且也有很多有相同功能的命令需要我们进行取舍，以至于代码的规范很难统一。</p>
<p>考虑到上面的这些原因，我查阅了一些相关的文档，发现这些问题其实很多人都考虑过，而且  也形成了一些不错的文章，但是还是有点零散。因此我就在这里把这些文章稍微整理了一下，作为以后我自己写脚本的技术规范。</p>
<h4 id="代码风格规范"><a href="#代码风格规范" class="headerlink" title="代码风格规范"></a>代码风格规范</h4><p>开头有“蛇棒”</p>
<p>所谓shebang其实就是在很多脚本的第一行出现的以#!开头的注释，他指明了当我们没有指定解释器的时候默认的解释器，一般可能是下面这样：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#!&#x2F;bin&#x2F;bash  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>当然，解释器有很多种，除了bash之外，我们可以用下面的命令查看本机支持的解释器:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ cat &#x2F;etc&#x2F;shells  </span><br><span class="line">#&#x2F;etc&#x2F;shells: valid login shells  </span><br><span class="line">&#x2F;bin&#x2F;sh  </span><br><span class="line">&#x2F;bin&#x2F;dash  </span><br><span class="line">&#x2F;bin&#x2F;bash  </span><br><span class="line">&#x2F;bin&#x2F;rbash  </span><br><span class="line">&#x2F;usr&#x2F;bin&#x2F;screen  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>当我们直接使用./a.sh来执行这个脚本的时候，如果没有shebang，那么它就会默认用$SHELL指定的解释器，否则就会用shebang指定的解释器。</p>
<p>这种方式是我们推荐的使用方式。</p>
<h4 id="代码有注释"><a href="#代码有注释" class="headerlink" title="代码有注释"></a>代码有注释</h4><p>注释，显然是一个常识，不过这里还是要再强调一下，这个在shell脚本里尤为重要。因为很多单行的shell命令不是那么浅显易懂，没有注释的话在维护起来会让人尤其的头大。</p>
<p>注释的意义不仅在于解释用途，而在于告诉我们注意事项，就像是一个README。</p>
<p>具体的来说，对于shell脚本，注释一般包括下面几个部分：</p>
<ul>
<li><p>shebang</p>
</li>
<li><p>脚本的参数</p>
</li>
<li><p>脚本的用途</p>
</li>
<li><p>脚本的注意事项</p>
</li>
<li><p>脚本的写作时间，作者，版权等</p>
</li>
<li><p>各个函数前的说明注释</p>
</li>
<li><p>一些较复杂的单行命令注释</p>
</li>
</ul>
<h4 id="参数要规范"><a href="#参数要规范" class="headerlink" title="参数要规范"></a>参数要规范</h4><p>这一点很重要，当我们的脚本需要接受参数的时候，我们一定要先判断参数是否合乎规范，并给出合适的回显，方便使用者了解参数的使用。</p>
<p>最少，最少，我们至少得判断下参数的个数吧：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">if [[ $# !&#x3D; 2 ]];then  </span><br><span class="line">    echo &quot;Parameter incorrect.&quot;  </span><br><span class="line">    exit 1  </span><br><span class="line">fi  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h4 id="变量和魔数"><a href="#变量和魔数" class="headerlink" title="变量和魔数"></a>变量和魔数</h4><p>一般情况下我们会将一些重要的环境变量定义在开头，确保这些变量的存在。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">source &#x2F;etc&#x2F;profile  </span><br><span class="line">export PATH&#x3D;”&#x2F;usr&#x2F;local&#x2F;bin:&#x2F;usr&#x2F;bin:&#x2F;bin:&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;sbin:&#x2F;sbin:&#x2F;apps&#x2F;bin&#x2F;”  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>这种定义方式有一个很常见的用途，最典型的应用就是，当我们本地安装了很多java版本时，我们可能需要指定一个java来用。那么这时我们就会在脚本开头重新定义JAVA_HOME以及PATH变量来进行控制。同时，一段好的代码通常是不会有很多硬编码在代码里的“魔数”的。如果一定要有，通常是用一个变量的形式定义在开头，然后调用的时候直接调用这个变量，这样方便日后的修改。</p>
<h4 id="缩进有规矩"><a href="#缩进有规矩" class="headerlink" title="缩进有规矩"></a>缩进有规矩</h4><p>对于shell脚本，缩进是个大问题。因为很多需要缩进的地方(比如if,for语句)都不长，所有很多人都懒得去缩进，而且很多人不习惯用函数，导致缩进功能被弱化。</p>
<p>其实正确的缩进是很重要的，尤其是在写函数的时候，否则我们在阅读的时候很容易把函数体跟直接执行的命令搞混。</p>
<p>常见的缩进方法主要有”soft tab”和”hard tab”两种。</p>
<ul>
<li><p>所谓soft tab就是使用n个空格进行缩进(n通常是2或4)</p>
</li>
<li><p>所谓hard tab当然就是指真实的\t字符</p>
</li>
<li><p>这里不去撕哪种方式最好，只能说各有各的优劣。反正我习惯用hard tab。</p>
</li>
<li><p>对于if和for语句之类的，我们最好不要把then，do这些关键字单独写一行，这样看上去比较丑。。。</p>
</li>
</ul>
<h4 id="命名有标准"><a href="#命名有标准" class="headerlink" title="命名有标准"></a>命名有标准</h4><p>所谓命名规范，基本包含下面这几点：</p>
<ul>
<li><p>文件名规范，以.sh结尾，方便识别</p>
</li>
<li><p>变量名字要有含义，不要拼错</p>
</li>
<li><p>统一命名风格，写shell一般用小写字母加下划线</p>
</li>
</ul>
<h4 id="编码要统一"><a href="#编码要统一" class="headerlink" title="编码要统一"></a>编码要统一</h4><p>在写脚本的时候尽量使用UTF-8编码，能够支持中文等一些奇奇怪怪的字符。不过虽然能写中文，但是在写注释以及打log的时候还是尽量英文，毕竟很多机器还是没有直接支持中文的，打出来可能会有乱码。这里还尤其需要注意一点，就是当我们是在windows下用utf-8编码来写shell脚本的时候，一定要注意这个utf-8是否是有BOM的。默认情况下windows判断utf-8格式是通过在文件开头加上三个EF BB BF字节来判断的，但是在Linux中默认是无BOM的。因此如果我们是在windows下写脚本的时候，一定要注意将编码改成Utf-8无BOM，一般用notepad++之类的编辑器都能改。否则，在Linux下运行的时候就会识别到开头的三个字符，从而报一些无法识别命令的错。当然，对于跨平台写脚本还有一个比较常见的问题就是换行符不同。windows默认是\r\n而unix下是\n。不过有两个小工具可以非常方便的解决这个问题：dos2unix,unix2dos。</p>
<h5 id="权限记得加"><a href="#权限记得加" class="headerlink" title="权限记得加"></a>权限记得加</h5><p>这一点虽然很小，但是我个人却经常忘记，不加执行权限会导致无法直接执行，有点讨厌。。。</p>
<h6 id="日志和回显"><a href="#日志和回显" class="headerlink" title="日志和回显"></a>日志和回显</h6><p>日志的重要性不必多说，能够方便我们回头纠错，在大型的项目里是非常重要的。</p>
<p>如果这个脚本是供用户直接在命令行使用的，那么我们最好还要能够在执行时实时回显执行过程，方便用户掌控。</p>
<p>有时候为了提高用户体验，我们会在回显中添加一些特效，比如颜色啊，闪烁啊之类的，具体可以参考ANSI/VT100 Control sequences这篇文章的介绍。</p>
<h6 id="密码要移除"><a href="#密码要移除" class="headerlink" title="密码要移除"></a>密码要移除</h6><p>不要把密码硬编码在脚本里，不要把密码硬编码在脚本里，不要把密码硬编码在脚本里。</p>
<p>重要的事情说三遍，尤其是当脚本托管在类似Github这类平台中时。。。</p>
<h6 id="太长要分行"><a href="#太长要分行" class="headerlink" title="太长要分行"></a>太长要分行</h6><p>在调用某些程序的时候，参数可能会很长，这时候为了保证较好的阅读体验，我们可以用反斜杠来分行：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">.&#x2F;configure \  </span><br><span class="line">–prefix&#x3D;&#x2F;usr \  </span><br><span class="line">–sbin-path&#x3D;&#x2F;usr&#x2F;sbin&#x2F;nginx \  </span><br><span class="line">–conf-path&#x3D;&#x2F;etc&#x2F;nginx&#x2F;nginx.conf \  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>注意在反斜杠前有个空格。</p>
<h4 id="编码细节规范"><a href="#编码细节规范" class="headerlink" title="编码细节规范"></a>编码细节规范</h4><p><strong>代码有效率</strong></p>
<p>在使用命令的时候要了解命令的具体做法，尤其当数据处理量大的时候，要时刻考虑该命令是否会影响效率。</p>
<p>比如下面的两个sed命令：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sed -n &#39;1p&#39; file  </span><br><span class="line">sed -n &#39;1p;1q&#39; file  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>他们的作用一样，都是获取文件的第一行。但是第一条命令会读取整个文件，而第二条命令只读取第一行。当文件很大的时候，仅仅是这样一条命令不一样就会造成巨大的效率差异。</p>
<p>当然，这里只是为了举一个例子，这个例子真正正确的用法应该是使用head -n1 file命令。。。</p>
<h5 id="勤用双引号"><a href="#勤用双引号" class="headerlink" title="勤用双引号"></a>勤用双引号</h5><p>几乎所有的大佬都推荐在使用”$”来获取变量的时候最好加上双引号。</p>
<p>不加上双引号在很多情况下都会造成很大的麻烦，为什么呢？举一个例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">#!&#x2F;bin&#x2F;sh  </span><br><span class="line">#已知当前文件夹有一个a.sh的文件  </span><br><span class="line">var&#x3D;&quot;*.sh&quot;  </span><br><span class="line">echo $var  </span><br><span class="line">echo &quot;$var&quot;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>他的运行结果如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">a.sh  </span><br><span class="line">*.sh  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>为啥会这样呢？其实可以解释为他执行了下面的命令：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">echo *.sh  </span><br><span class="line">echo &quot;*.sh&quot;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>在很多情况下，在将变量作为参数的时候，一定要注意上面这一点，仔细体会其中的差异。上面只是一个非常小的例子，实际应用的时候由于这个细节导致的问题实在是太多了。。。</p>
<h5 id="巧用main函数"><a href="#巧用main函数" class="headerlink" title="巧用main函数"></a>巧用main函数</h5><p>我们知道，像java，C这样的编译型语言都会有一个函数入口，这种结构使得代码可读性很强，我们知道哪些直接执行，那些是函数。但是脚本不一样，脚本属于解释性语言，从第一行直接执行到最后一行，如果在这当中命令与函数糅杂在一起，那就非常难读了。</p>
<p>用python的朋友都知道，一个合乎标准的python脚本大体上至少是这样的：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#!&#x2F;usr&#x2F;bin&#x2F;env python  </span><br><span class="line">  </span><br><span class="line">def func1():  </span><br><span class="line">    pass  </span><br><span class="line">def func2():  </span><br><span class="line">    pass  </span><br><span class="line">if __name__&#x3D;&#x3D;&#39;__main__&#39;:  </span><br><span class="line">    func1()  </span><br><span class="line">    func2()  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>他用一个很巧妙的方法实现了我们习惯的main函数，使得代码可读性更强。</p>
<p>在shell中，我们也有类似的小技巧:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">#!&#x2F;usr&#x2F;bin&#x2F;env bash  </span><br><span class="line">  </span><br><span class="line">func1()&#123;  </span><br><span class="line">    #do sth  </span><br><span class="line">&#125;  </span><br><span class="line">func2()&#123;  </span><br><span class="line">    #do sth  </span><br><span class="line">&#125;  </span><br><span class="line">main()&#123;  </span><br><span class="line">    func1  </span><br><span class="line">    func2  </span><br><span class="line">&#125;  </span><br><span class="line">main &quot;$@&quot;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>我们可以采用这种写法，同样实现类似的main函数，使得脚本的结构化程度更好。</p>
<h5 id="考虑作用域"><a href="#考虑作用域" class="headerlink" title="考虑作用域"></a>考虑作用域</h5><p>shell中默认的变量作用域都是全局的，比如下面的脚本：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#!&#x2F;usr&#x2F;bin&#x2F;env bash  </span><br><span class="line">  </span><br><span class="line">var&#x3D;1  </span><br><span class="line">func()&#123;  </span><br><span class="line">    var&#x3D;2  </span><br><span class="line">&#125;  </span><br><span class="line">func  </span><br><span class="line">echo $var  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>他的输出结果就是2而不是1，这样显然不符合我们的编码习惯，很容易造成一些问题。</p>
<p>因此，相比直接使用全局变量，我们最好使用local readonly这类的命令，其次我们可以使用declare来声明变量。这些方式都比使用全局方式定义要好。</p>
<h5 id="函数返回值"><a href="#函数返回值" class="headerlink" title="函数返回值"></a>函数返回值</h5><p>在使用函数的时候一定要注意，shell中函数的返回值只能是整数，估计是因为一般情况下一个函数的返回值通常表示这个函数的运行状态，所以一般都是0或者是１就够了，因此就设计成了这样。不过，如果非得想传递字符串，也可以通过下面变通的方法:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">func()&#123;  </span><br><span class="line">    echo &quot;2333&quot;  </span><br><span class="line">&#125;  </span><br><span class="line">res&#x3D;$(func)  </span><br><span class="line">echo &quot;This is from $res.&quot;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>这样，通过echo或者print之类的就可以做到传一些额外参数的目的。</p>
<h5 id="间接引用值"><a href="#间接引用值" class="headerlink" title="间接引用值"></a>间接引用值</h5><p>什么叫间接引用？比如下面这个场景：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">VAR1&#x3D;&quot;2323232&quot;  </span><br><span class="line">VAR2&#x3D;&quot;VAR1&quot;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>我们有一个变量VAR1，又有一个变量VAR2，这个VAR2的值是VAR1的名字，那么我们现在想通过VAR2来获取VAR1的值，这时候应该怎么办呢？</p>
<p>比较土鳖的方法是这样：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">eval echo \$$VAR2  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>啥意思呢？其实就是构造了一个字符串echo XXX，这个XXX就是XXX”，这个XXX就是VAR2的值VAR1，然后再用eval强制解析，这样就做到了变相取值。</p>
<p>这个用法的确可行，但是看起来十分的不舒服，很难直观的去理解，我们并不推荐。而且事实上我们本身就不推荐使用eval这个命令。</p>
<p>比较舒服的写法是下面这样:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">echo $&#123;!VAR1&#125;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>通过在变量名前加一个!就可以做到简单的间接引用了。</p>
<p>不过需要注意的是，用上面的方法，我们只能够做到取值，而不能做到赋值。如果想要做到赋值，还要老老实实的用eval来处理:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">VAR1&#x3D;VAR2  </span><br><span class="line">eval $VAR1&#x3D;233  </span><br><span class="line">echo $VAR2  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h5 id="巧用heredocs"><a href="#巧用heredocs" class="headerlink" title="巧用heredocs"></a>巧用heredocs</h5><p>所谓heredocs，也可以算是一种多行输入的方法，即在”&lt;&lt;”后定一个标识符，接着我们可以输入多行内容，直到再次遇到标识符为止。</p>
<p>使用heredocs，我们可以非常方便的生成一些模板文件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">cat&gt;&gt;&#x2F;etc&#x2F;rsyncd.conf &lt;&lt; EOF  </span><br><span class="line">log file &#x3D; &#x2F;usr&#x2F;local&#x2F;logs&#x2F;rsyncd.log  </span><br><span class="line">transfer logging &#x3D; yes  </span><br><span class="line">log format &#x3D; %t %a %m %f %b  </span><br><span class="line">syslog facility &#x3D; local3  </span><br><span class="line">EOF  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h5 id="学会查路径"><a href="#学会查路径" class="headerlink" title="学会查路径"></a>学会查路径</h5><p>很多情况下，我们会先获取当前脚本的路径，然后一这个路径为基准，去找其他的路径。通常我们是直接用pwd以期获得脚本的路径。</p>
<p>不过其实这样是不严谨的，pwd获得的是当前shell的执行路径，而不是当前脚本的执行路径。</p>
<p>正确的做法应该是下面这两种：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">script_dir&#x3D;$(cd $(dirname $0) &amp;&amp; pwd)  </span><br><span class="line">script_dir&#x3D;$(dirname $(readlink -f $0 ))  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>应当先cd进当前脚本的目录然后再pwd，或者直接读取当前脚本的所在路径。</p>
<h5 id="代码要简短"><a href="#代码要简短" class="headerlink" title="代码要简短"></a>代码要简短</h5><p>这里的简短不单单是指代码长度，而是只用到的命令数。原则上我们应当做到，能一条命令解决的问题绝不用两条命令解决。这不仅牵涉到代码的可读性，而且也关乎代码的执行效率。</p>
<p>最最经典的例子如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cat &#x2F;etc&#x2F;passwd | grep root  </span><br><span class="line">grep root &#x2F;etc&#x2F;passwd  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>cat命令最为人不齿的用法就是这样，用的没有任何意义，明明一条命令可以解决，他非得加根管道。。。</p>
<p>其实代码简短在还能某种程度上能保证效率的提升，比如下面的例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#method1  </span><br><span class="line">find . -name &#39;*.txt&#39; |xargs sed -i s&#x2F;233&#x2F;666&#x2F;g  </span><br><span class="line">find . -name &#39;*.txt&#39; |xargs sed -i s&#x2F;235&#x2F;626&#x2F;g  </span><br><span class="line">find . -name &#39;*.txt&#39; |xargs sed -i s&#x2F;333&#x2F;616&#x2F;g  </span><br><span class="line">find . -name &#39;*.txt&#39; |xargs sed -i s&#x2F;233&#x2F;664&#x2F;g  </span><br><span class="line">#method1  </span><br><span class="line">find . -name &#39;*.txt&#39; |  </span><br><span class="line">xargs sed -i &quot;s&#x2F;233&#x2F;666&#x2F;g;s&#x2F;235&#x2F;626&#x2F;g;s&#x2F;333&#x2F;616&#x2F;g;s&#x2F;233&#x2F;664&#x2F;g&quot;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>这两种方法做的事情都一样，就是查找所有的.txt后缀的文件并做一系列替换。前者是多次执行find，后者是执行一次find，但是增加了sed的模式串。第一种更直观一点，但是当替换的量变大的时候，第二种的速度就会比第一种快很多。这里效率提升的原因，就是第二种只要执行一次命令，而第一种要执行多次。并且，巧用xargs命令，我们还可以十分方便的进行并行化处理:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find . -name &#39;*.txt&#39; |xargs -P $(nproc) sed -i &quot;s&#x2F;233&#x2F;666&#x2F;g;s&#x2F;235&#x2F;626&#x2F;g;s&#x2F;333&#x2F;616&#x2F;g;s&#x2F;233&#x2F;664&#x2F;g&quot;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>通过-P参数指定并行度，可以进一步加快执行效率。</p>
<h5 id="命令并行化"><a href="#命令并行化" class="headerlink" title="命令并行化"></a>命令并行化</h5><p>当我们需要充分考虑执行效率时，我们可能需要在执行命令的时候考虑并行化。shell中最简单的并行化是通过”&amp;”以及”wait”命令来做:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">func()&#123;  </span><br><span class="line">    #do sth  </span><br><span class="line">｝  </span><br><span class="line">for((i&#x3D;0;i&lt;10;i++))do  </span><br><span class="line">    func &amp;  </span><br><span class="line">done  </span><br><span class="line">wait  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>当然，这里并行的次数不能太多，否则机器会卡死。稍微正确的做法比较复杂，以后再讨论，如果图省事可以使用parallel命令来做，或者是用上面提到的xargs来处理。</p>
<h5 id="全文本检索"><a href="#全文本检索" class="headerlink" title="全文本检索"></a>全文本检索</h5><p>我们知道，当我们想在文件夹下所有的txt文件中检索某一个字符串(比如233)的时候，我们可能会用类似这样的命令：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find . -name &#39;*.txt&#39; -type f | xargs grep 2333  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>很多情况下，这个命令会想我们所想的找到对应的匹配行，但是我们需要注意两个小问题。</p>
<p>find命令会符合要求的匹配文件名，但是如果文件名包含空格，这时候将文件名传给grep的时候就会有问题，这个文件就会被当成两个参数，这时候就要加一层处理，保证用空格分开的文件名不会被当成两个参数：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find . -type f|xargs -i echo &#39;&quot;&#123;&#125;&quot;&#39;|xargs grep 2333  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>有时候，文件的字符集可能跟终端的字符集不一致，这时候就会导致grep在搜索时将文件当成二进制文件从而报binary file matches之类的问题。这时候要么用iconv之类的字符集转换工具将字符集进行切换，要么就在不影响查找的情况下对grep加-a参数，将所有文件看成文本文件:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find . -type f|xargs grep -a 2333  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h4 id="使用新写法"><a href="#使用新写法" class="headerlink" title="使用新写法"></a>使用新写法</h4><p>这里的新写法不是指有多厉害，而是指我们可能更希望使用较新引入的一些语法，更多是偏向代码风格的，比如</p>
<p>尽量使用func(){}来定义函数，而不是func{}</p>
<p>尽量使用[[]]来代替[]</p>
<p>尽量使用$()将命令的结果赋给变量，而不是反引号</p>
<p>在复杂的场景下尽量使用printf代替echo进行回显</p>
<p>事实上，这些新写法很多功能都比旧的写法要强大，用的时候就知道了。</p>
<h5 id="其他小tip"><a href="#其他小tip" class="headerlink" title="其他小tip"></a>其他小tip</h5><p>考虑到还有很多零碎的点，就不一一展开了，这里简单提一提。</p>
<p>路径尽量保持绝对路径，绝多路径不容易出错，如果非要用相对路径，最好用./修饰</p>
<p>优先使用bash的变量替换代替awk sed，这样更加简短</p>
<p>简单的if尽量使用&amp;&amp; ||，写成单行。</p>
<p>比如[[ x &gt; 2]] &amp;&amp; echo x</p>
<p>当export变量时，尽量加上子脚本的namespace，保证变量不冲突</p>
<p>会使用trap捕获信号，并在接受到终止信号时执行一些收尾工作</p>
<p>使用mktemp生成临时文件或文件夹</p>
<p>利用/dev/null过滤不友好的输出信息</p>
<p>会利用命令的返回值判断命令的执行情况</p>
<p>使用文件前要判断文件是否存在，否则做好异常处理</p>
<p>不要处理ls后的数据(比如ls -l | awk ‘{ print $8 }’)，ls的结果非常不确定，并且平台有关</p>
<p>读取文件时不要使用for loop而要使用while read</p>
<p>使用cp -r命令复制文件夹的时候要注意如果目的文件夹不存在则会创建，如果存在则会复制到该文件的子文件夹下</p>
<h4 id="静态检查工具shellcheck"><a href="#静态检查工具shellcheck" class="headerlink" title="静态检查工具shellcheck"></a><strong>静态检查工具shellcheck</strong></h4><h6 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h6><p>为了从制度上保证脚本的质量，我们最简单的想法大概就是搞一个静态检查工具，通过引入工具来弥补开发者可能存在的知识盲点。</p>
<p>市面上对于shell的静态检查工具还真不多，找来找去就找到一个叫shellcheck的工具，开源在github上，有8K多的star，看上去还是十分靠谱的。我们可以去他的主页了解具体的安装和使用信息。</p>
<h6 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h6><p>这个工具的对不同平台的支持力度都很大，他至少支持了Debian,Arch,Gentoo,EPEL,Fedora,OS X,openSUSE等等各种的平台的主流包管理工具。安装方便。具体可以参照安装文档</p>
<h6 id="集成"><a href="#集成" class="headerlink" title="集成"></a>集成</h6><p>既然是静态检查工具，就一定可以集成在CI框架里，shellcheck可以非常方便的集成在Travis CI中，供以shell脚本为主语言的项目进行静态检查。</p>
<h6 id="样例"><a href="#样例" class="headerlink" title="样例"></a>样例</h6><p>在文档的Gallery of bad code里，也提供了非常详细的“坏代码”的标准，具有非常不错的参考价值，可以在闲下来的时候当成”Java Puzzlers“之类的书来读读还是很惬意的。</p>
<h6 id="本质"><a href="#本质" class="headerlink" title="本质"></a>本质</h6><p>不过，其实我觉得这个项目最最精华的部分都不是上面的功能，而是他提供了一个非常非常强大的wiki。在这个wiki里，我们可以找到这个工具所有判断的依据。在这里，每一个检测到的问题都可以在wiki里找到对应的问题单号，他不仅告诉我们”这样写不好”，而且告诉我们”为什么这样写不好”，”我们应当怎么写才好”，非常适合刨根问底党进一步研究。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/linux/" rel="tag">linux</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-mysql/03.为Navicat正名，Navicat完全使用手册"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/mysql/03.%E4%B8%BANavicat%E6%AD%A3%E5%90%8D%EF%BC%8CNavicat%E5%AE%8C%E5%85%A8%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/"
    >03.为Navicat正名，Navicat完全使用手册.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/mysql/03.%E4%B8%BANavicat%E6%AD%A3%E5%90%8D%EF%BC%8CNavicat%E5%AE%8C%E5%85%A8%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/mysql/">mysql</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="为Navicat正名，Navicat完全使用手册"><a href="#为Navicat正名，Navicat完全使用手册" class="headerlink" title="为Navicat正名，Navicat完全使用手册"></a>为Navicat正名，Navicat完全使用手册</h1><p>最近使用了DataGrip一段时间，发现它并不适合我，因为我平时不仅仅要写代码，也要负责数据仓库的建设和数据库管理的一些工作。</p>
<p>这里给小伙伴们罗列几个我在使用DataGrip中一些不足的地方：</p>
<ul>
<li>用户体验并不十分友好，你需要花费一些时间去学习如何使用它</li>
<li>和相似的工具比较，它运行并不迅速，当处理大容量数据库的时候会比较缓慢，将耗费GB等级的内存</li>
<li>完全加载本地数据库数据，如果你的数据库数据较多，可能等它加载完成，你差不多要吃午饭了</li>
<li>导入导出功能有待完善</li>
</ul>
<p>最后还是滚去用Navicat了</p>
<p>Navicat的功能非常多，这里为了让小伙伴们一一掌握，分为多个篇章具体讲述。</p>
<p><strong>目录</strong></p>
<ul>
<li>安装Navicat</li>
<li>连接不同数据库</li>
<li>创建数据库</li>
<li>数据传输</li>
<li>导出表结构</li>
<li>生成数据字典</li>
<li>查找数据或表名</li>
<li>生成E-R模型</li>
</ul>
<p><em>注：以上功能无需书写任何SQL代码</em></p>
<p><strong>安装Navicat</strong></p>
<p>因为工作原因，需要经常连接不同的数据库，这里我安装的是Navicat Premium版本，在公众号后台回复 <strong>Navicat</strong> 即可获取。</p>
<p>安装过程很简单，只要你能看懂中文，不停的下一步，等待安装完成即可。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826061.png" alt="img"></p>
<p><strong>连接不同数据库</strong></p>
<p>这里分别介绍Oracle，MySQL，SQL Server这三种数据库的连接方法</p>
<p><strong>连接Oracle</strong></p>
<p>在连接Oracle之前我们需要先配置一下OCI文件，具体如下：</p>
<p>点开主菜单里的【工具】——【选项…】,在弹出的对话框中找的OCI选项</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825677.png" alt="img"></p>
<p>这里就要把你安装的oracle的安装目录下的bin文件夹里面的oci.dll文件导入</p>
<p>E:\software\oracle\Oracle_win64\product\11.2.0\dbhome_1\BIN\oci.dll</p>
<p>以上是我的安装目录下的文件，大家根据自己的安装目录不同来导进来</p>
<p>导进来之后就重启Navicat，重启后会自动生效。</p>
<p>重启Navicat后，点击菜单栏的连接，选择Oracle，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825822.png" alt="img"></p>
<p>在弹出的对话框中做如下配置，其中连接名可以随意起，然后输入你要连接的主机名或IP地址，最后输入账号密码。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826062.png" alt="img"></p>
<p>如果你是使用DBA角色登录的，还需要点开高级选项卡，将角色选为DBA</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825622.png" alt="img"></p>
<p>配置完了以后，我们可以点击连接测试</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826063.png" alt="img">这样Oracle的连接就配置好了。</p>
<p><strong>连接MySQL和SQL Server</strong></p>
<p>MySQL和SQL Server的连接相对比较简单，只需要输入主机名或IP地址，账号密码即可，具体如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826062.png" alt="img"></p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825945.png" alt="img"></p>
<p>配置好后，点击连接测试，在弹出连接成功窗口就表示我们配置好了。</p>
<p>数据库连接成功后，下面是各个功能的介绍，我们以SQL Server数据库作为案例来介绍各个功能的使用。</p>
<p><strong>创建数据库</strong></p>
<p>右键我们刚新建的数据库连接——选择【新建数据库…】即可开始创建数据库了。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020956041.png" alt="img"></p>
<p>可以根据你的要求来进行配置，配置完以后，还可以在【SQL预览】看到完整的SQL代码：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825745.png" alt="img"></p>
<p><strong>数据传输</strong></p>
<p>数据传输，顾名思义就是将数据从其他地方传输到当前数据库，例如我们将SQL_Road数据库中的数据传输到刚才新建的Test数据库中。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826062.png" alt="img"></p>
<p>右键刚才创建好的Test数据库，在弹出的选项中选择【数据传输…】，在弹出的窗口中我们配置源数据库为【SQL_Road】,目标数据库配置为【Test】，这样就可以将SQL_Road中的对象传输到Test了，支持传输的对象包括：表，视图，函数和存储过程，具体如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825681.webp" alt="img"></p>
<p>点击开始即可开始进行数据传输，传输效率还是很快的，即使你的数据量很大，也可以使用该方法进行数据传输，这里使用了不到2秒钟就将整个数据库对象迁移到新的数据库中了。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825780.png" alt="img"></p>
<p>此外如果你想导出整个数据库中的对象到文件，也可以使用该方法，只需要将方式改为文件即可，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825787.png" alt="img"></p>
<p>如果你想自定义导出的内容，可以点击该窗口的【高级】选项进行自定义配置，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825781.png" alt="img"></p>
<p>配置完成后，点击开始，就会将你选择的数据库对象以脚本的形式导出到文件中。</p>
<p>但是数据传输到文件有个不足的地方，就是会将数据记录也会一并导出，而很多时候，我们其实只需要表结构，那么我们可以使用下面的这个功能。</p>
<p><strong>转储SQL文件</strong></p>
<p>双击打开数据库后，我们右键架构名dbo，在弹出的选项中选择【转储SQL文件…】，这里还有两个子选项：【结构和数据…】和【仅结构…】，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826060.png" alt="img"></p>
<p>他们的区别就是一个有数据记录，一个没有数据记录，如果你只要数据结构可以只选择【仅结构…】，这样就可以大大节省导出时间了。</p>
<p><strong>打印模式</strong></p>
<p>做数据库仓库往往面对的不是几张表，往往是成百上千张数据表，该怎么维护对DBA是个非常头疼的事，如果有个数据表结构或数据字典之类的就非常完美了。</p>
<p>打印模式可以<strong>完美的生成所有表的数据表结构</strong>的。这里我们选择表数量较多的数据库ReportServer。右键dbo后，选择【打印模式…】，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825741.png" alt="img"></p>
<p>可以看到右侧窗口里面出现了所有数据表的表结构，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825714.png" alt="img"></p>
<p>足足有10页之多，我们点击左上方的【打印】，将表结构打印成PDF文件，就可以得到一份非常完成的数据字典了，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826061.png" alt="img"></p>
<p>内容中主键和索引也有仔细的标注出来，其中<strong>P代表主键</strong>，下面的<strong>index表示索引</strong>。</p>
<p>这个功能绝对是一个神器！</p>
<p><strong>在模式中查找</strong></p>
<p>这个功能主要用来查找数据或结果，当你需要从当前数据库中查找数据记录或对象中包含某些字符时，可以使用该功能，具体如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826062.png" alt="img"></p>
<p>在弹出的对话框中，我们查找pre的结构，就可以将当前数据库符合要求的的所有对象都查找出来，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825971.png" alt="img"></p>
<p><strong>逆向模式到模型</strong></p>
<p>这又是一个神仙技能，谁用谁知道，右键dbo在弹出的选项中选择【逆向模式到模型..】,如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020825970.png" alt="img"></p>
<p>它可以<strong>将当前数据库中创建的所有表，以E-R图的模型清楚的告诉你每个表之间的关联关系</strong>，包括主外键，表结构，关联关系等，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826058.png" alt="img"></p>
<p>你以为就这？</p>
<p>还支持<strong>模型转换，模型导出</strong>，说白了就是你可以<strong>将该模型转换成其他数据库的模型</strong>，从SQL Server转换成MySQL，Oracle，Postgresql都可以。点击左上角的三短横，选择【文件】——【模型转换】，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826061.png" alt="img"></p>
<p>在弹出的对话框中选择你要转换的类型，比如我们想转换成MySQL 5.6的，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826066.webp" alt="img"></p>
<p>点击确定后，即可将当前的SQL Server模型立马转换成MySQL 5.6。给大家看下对比效果：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826067.png" alt="img"></p>
<p>SQL Server模型</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826064.png" alt="img"></p>
<p>MySQL模型</p>
<p>转换成功后可以直接将转换后的模型导出成对应的SQL。</p>
<p>点击【工具】——【导出SQL…】，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826017.png" alt="img"></p>
<p>选择导出位置，点确定即可，导出的SQL文件里面就是MySQL的建库代码了。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020970388.png" alt="img"></p>
<p>能导出肯定就可以打印，同样如果需要将模型文件保存，也可以导出为PDF，PNG，SVG等格式，但是建议打印成PNG图片格式，因为PDF会分页，导致模型不完整。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826066.png" alt="img"></p>
<p>打印出来的图片如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020826065.png" alt="img"></p>
<p>当然你可随意放大缩小查看详细信息。</p>
<p>以上我们主要介绍了数据库和架构的一些功能，下一期我们再着重介绍数据库对象的一些功能，敬请期待。</p>
<p>之前发布了Navicat数据库和架构的一些实用功能，还没看的小伙伴可以戳这里：</p>
<p>《<a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA3MTg4NjY4Mw==&mid=2457308741&idx=1&sn=ba3802779f1b4ded74187fda6bdbebd3&chksm=88a59c71bfd215673562781984f93ab350920c0ae4049ba80456cc428508207e311a1e022201&scene=21#wechat_redirect">为Navicat正名，Navicat完全使用手册（上）</a>》</p>
<p>今天我们继续介绍Navicat数据库对象的一些功能。</p>
<p><strong>新建表</strong></p>
<p>这个是比较常见的功能，相比其他管理工具，Navicat将建表过程中所涉及的各种常用功能都包含进去了，包含新字段，索引，主键，外键，唯一键等等与表有关的内容，具体如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020889785.png" alt="img"></p>
<p>针对表的几个常用功能，我们再详细给大家介绍一下</p>
<p><strong>字段</strong></p>
<p>字段的功能很多，也是其他功能的前提，意思是你必须先创建了字段才能使用后面的一些功能。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020889785.png" alt="img"></p>
<p>其中第一排的添加字段，插入字段，删除字段，可以通过字面意思来理解。</p>
<p>主键：选中某个字段，点击主键即可设置为主键</p>
<p>标识：勾选后一般用作自增长列来使用</p>
<p>注释：对字段的备注，通常为中文</p>
<p>名：字段存在表里的字段名</p>
<p>类型：字段数据类型</p>
<p>长度：字段的长度，通常int和datetime可以不设置长度。</p>
<p>比例：小数位数</p>
<p>不是Null：该字段是否为空，勾选不为空，不勾选为空。</p>
<p><strong>索引</strong></p>
<p>用来创建索引，只需要选定指定的列，就可以在对应的列上创建索引了。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020889786.png" alt="img"></p>
<p>名：索引的名称，通常按命名规范以index或idx开头，当然你也可以自定义。</p>
<p>字段：选择在哪个字段上创建索引。还可以选择排序规则，默认是升序ASC。</p>
<p>索引类型：不同的数据库索引类型不同，SQL Server类型一般为聚集索引（Clustered）和非聚集索引（Non-Clustered）</p>
<p>唯一键：用来限制字段的记录是否可以重复，勾选就是不可重复。</p>
<p><strong>外键</strong></p>
<p>用来创建外键约束的功能，目前的数据库设计中，通常不设置外键约束了。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020889786.png" alt="img"></p>
<p>名：外键名称，通常以fk开头</p>
<p>字段：用来设置外键的字段</p>
<p>参考表：与之相关联的表</p>
<p>参考字段：与之相关联表中的字段</p>
<p>删除时：是否级联删除</p>
<p>更新时：是否级联更新</p>
<p><strong>唯一键</strong></p>
<p>区别于主键，唯一键具有唯一性</p>
<p>与主键的区别有：</p>
<p>1.主键不允许空值，唯一索引允许空值</p>
<p>2.主键只允许一个，唯一索引允许多个</p>
<p>3.主键产生唯一的聚集索引，唯一索引产生唯一的非聚集索引</p>
<p><strong>检查</strong></p>
<p>用来约束数据插入的合法性，对应SQL中的CHECK或DEFAULT</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020889907.png" alt="img"></p>
<p><strong>触发器</strong></p>
<p>用来创建与表相关的触发器，其中触发器的使用可以查看历史文章：</p>
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzA3MTg4NjY4Mw==&mid=2457301529&idx=2&sn=b3881753e41e401ac374057bce52e768&chksm=88a5602dbfd2e93bcae925b00130230e7e8f1b957f29ce2f3e7471c4076ebba3bf36ad2b4ab1&scene=21#wechat_redirect">SQL高级知识V2——触发器</a></p>
<p><strong>选项</strong></p>
<p>这里主要用来设置自增长的种子值（起始值）和增量</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020889991.png" alt="img"></p>
<p><strong>SQL预览</strong></p>
<p>这里可以看到我们刚才对表做的配置所对应的SQL语句</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020889994.png" alt="img"></p>
<p><strong>查看对象模式</strong></p>
<p>创建好的表有三种查看方式：列表，详细信息和ER图，如下图</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890228.png" alt="img"></p>
<p><strong>列表模式</strong></p>
<p>只显示表名，是表的默认显示方式</p>
<p><strong>详细信息模式</strong></p>
<p>会显示除了表名以为的其他信息，记录数，修改日期和表的注释等</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890094.png" alt="img"></p>
<p><strong>ER图模式</strong></p>
<p>会以ER图的形式显示每个表之间的关联关系，如下图</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890228.png" alt="img"></p>
<p><strong>导入向导</strong></p>
<p>用来引导用户导入数据到数据库对应的表</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890229.png" alt="img"></p>
<p>我们打算将Customers.xlsx里的数据导入到表Customers中，具体操作如下：</p>
<p>在导入向导中选择要导入的文件类型，Navicat支持多种导入类型，这里我们选择导入Excel文档，然后点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890230.png" alt="img"></p>
<p>找的需要导入的文件，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890231.png" alt="img"></p>
<p>配置字段名等内容，然后点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890231.png" alt="img"></p>
<p>选择要导入的目标表，你也可以直接新建一张表，这里我们导入到目标表Orders，然后点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890231.png" alt="img"></p>
<p>配置源表和目标表之间的字段对应关系，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890233.png" alt="img"></p>
<p>选择一个导入模式，如果是直接添加，可以选择添加模式，如果是要先清空目标表再导入可以选择复制模式，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890232.png" alt="img"></p>
<p>点击开始就可以导入数据了</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890232.png" alt="img"></p>
<p><strong>导出向导</strong></p>
<p>需要将数据库里的数据导出可以使用导出向导，具体如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890232.png" alt="img"></p>
<p>在弹出的对话框中选择导出文件类型，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601021007458.png" alt="img"></p>
<p>选择需要导出的表，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890233.png" alt="img"></p>
<p>选择需要导出的列，默认全部字段，也可以选择指定字段，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890623.png" alt="img"></p>
<p>定义一些附加选项，导出包含标题，遇到导出错误继续等，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890623.png" alt="img"></p>
<p>点击开始执行导出操作</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890626.png" alt="img"></p>
<p><strong>新建查询</strong></p>
<p>新建查询有两种方式，可以直接右键选择新建查询，也可以点击菜单栏的查询-新建查询，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890625.png" alt="img"></p>
<p><strong>查询功能</strong></p>
<p>查询是我们用的比较频繁的一个功能，在查询窗口里面有许多好用的功能</p>
<p><strong>智能提醒</strong></p>
<p>根据我们输入的字符来自动提醒一些关键字，字段名，表名，视图名，函数名等，如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890623.png" alt="img"></p>
<p><strong>美化SQL</strong></p>
<p>当你看到一段代码写的很复杂且都堆在一起，可以使用美化SQL功能，一键帮你把堆在一起的SQL美化得整整齐齐。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890624.png" alt="img"></p>
<p>美化前</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890624.png" alt="img"></p>
<p>美化后</p>
<p><strong>其他功能</strong></p>
<p>此外查询窗口还有很多其他功能，都集中在左上角的三短横上面，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890627.png" alt="img"></p>
<p>这里将查询窗口的所有功能都汇集在一起，比如注释，查看执行计划，简化SQL等都在这里可以找到，小伙伴们可以一一尝试一下。</p>
<p><strong>创建视图</strong></p>
<p>Navicat提供一套创建视图的模板，主要是针对新手朋友。如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020941357.png" alt="img"></p>
<p>这里我们只需要写查询语句即可，在SQL预览里可以看到完整的SQL语句。如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890626.png" alt="img"></p>
<p>点击预览，可以看到我们刚定义的视图结果，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890626.png" alt="img"></p>
<p><strong>创建函数和存储过程</strong></p>
<p>函数和存储过程是类似的，区别是函数必须要有返回值，而存储过程可有可无。</p>
<p>他们也有一套模板，选择函数后点击新建函数，弹出如下对话框：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890633.png" alt="img"></p>
<p>输入函数名或过程名，然后选择你想是想创建函数还是存储过程。然后输入参数名称和类型，也可以不带参数，点击下一步</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890627.png" alt="img"></p>
<p>函数必须要有返回值，这里需要定义返回值类型，点击下一步或完成。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020891065.png" alt="img"></p>
<p>这里会配置函数的一些附加信息，通常不用配置，直接点击完成即可。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890632.png" alt="img"></p>
<p>然后我们就可以看到函数的编辑窗口了</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890632.png" alt="img"></p>
<p>写好函数里的语句后点击运行即可开始测试了。</p>
<p><strong>备份和还原</strong></p>
<p>通过Navicat，我们可以很方便的完成备份和还原操作，我们先来备份一个文件。</p>
<p><strong>备份</strong></p>
<p>点击菜单栏的SQL Server备份——新建备份，在弹出的窗口中配置备份内容，然后点击生成SQL即可。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890632.png" alt="img"></p>
<p>在这里我们可以看到备份的代码，然后点备份就会开始备份</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890634.png" alt="img"></p>
<p><strong>还原</strong></p>
<p>在备份完成后，我们可以选择还原该备份包</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890633.png" alt="img"></p>
<p>在弹出的对话框中可以配置相关信息</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890633.png" alt="img"></p>
<p>点击生成SQL就会看到执行备份的SQL命令，点击还原即可开始执行</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890634.png" alt="img"></p>
<p><strong>历史日志记录</strong></p>
<p>该功能可以很方便的查看到你在数据库中执行了哪些操作。点击菜单栏中的工具——历史日志，就可以看到我们的操作记录了，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601020890635.png" alt="img"></p>
<p>以上就是我们常用的一些功能了，新版本的Navicat还有一些数据报表统计功能，感兴趣的小伙伴可以下载体验一下。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mysql/" rel="tag">mysql</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-mysql/MySQL索引详解"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/mysql/MySQL%E7%B4%A2%E5%BC%95%E8%AF%A6%E8%A7%A3/"
    >MySQL索引详解.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/mysql/MySQL%E7%B4%A2%E5%BC%95%E8%AF%A6%E8%A7%A3/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/mysql/">mysql</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="MySQL索引详解"><a href="#MySQL索引详解" class="headerlink" title="MySQL索引详解"></a>MySQL索引详解</h1><p><strong>一、什么是索引？</strong></p>
<p>索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分)，它们包含着对数据表里所有记录的引用指针。</p>
<p>索引是一种数据结构。数据库索引，是数据库管理系统中一个排序的数据结构，以协助快速查询、更新数据库表中数据；索引的实现通常使用B树及其变种B+树。</p>
<p>更通俗的说，索引就相当于目录。为了方便查找书中的内容，通过对内容建立索引形成目录。索引是一个文件，它是要占据物理空间的。</p>
<p><strong>二、索引有哪些优缺点？</strong></p>
<p>索引的优点</p>
<p>1.可以大大加快数据的检索速度，这也是创建索引的最主要的原因。</p>
<p>2.通过使用索引，可以在查询的过程中，使用优化隐藏器，提高系统的性能。</p>
<p>索引的缺点</p>
<p>1.时间方面：创建索引和维护索引要耗费时间，具体地，当对表中的数据进行增加、删除和修改的时候，索引也要动态的维护，会降低增/改/删的执行效率；</p>
<p>2.空间方面：索引需要占物理空间。</p>
<h3 id="三、索引使用场景（重点）"><a href="#三、索引使用场景（重点）" class="headerlink" title="三、索引使用场景（重点）"></a>三、索引使用场景（重点）</h3><p>Where</p>
<p><img src="http://iubest.gitee.io/pic/640-1601168451037.webp" alt="img"></p>
<p>上图中，根据id查询记录，因为ID字段仅建立了主键索引，因此此SQL执行可选的索引只有主键索引，如果有多个，最终会选一个较优的作为检索的依据。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">-- 增加一个没有建立索引的字段</span><br><span class="line">alter table innodb1 add sex char(1);</span><br><span class="line">-- 按sex检索时可选的索引为null</span><br><span class="line">EXPLAIN SELECT * from innodb1 where sex&#x3D;&#39;男&#39;;</span><br></pre></td></tr></table></figure>



<p><img src="http://iubest.gitee.io/pic/640-1601168450598.webp" alt="img"></p>
<p>可以尝试在一个字段未建立索引时，根据该字段查询的效率，然后对该字段建立索引（alter table 表名 add index 字段名），同样的SQL执行的效率，你会发现查询效率会有明显的提升（数据量越大越明显）。</p>
<p>ORDER BY</p>
<p>当我们使用order by将查询结果按照某个字段排序时，如果该字段没有建立索引，那么执行计划会将查询出的所有数据使用外部排序（将数据从硬盘分批读取到内存使用内部排序，最后合并排序结果），这个操作是很影响性能的，因为需要将查询涉及到的所有数据从磁盘中读到内存（如果单条数据过大或者数据量过多都会降低效率），更无论读到内存之后的排序了。</p>
<p>但是如果我们对该字段建立索引alter table 表名 add index(字段名)，那么由于索引本身是有序的，因此直接按照索引的顺序和映射关系逐条取出数据即可。而且如果分页的，那么只用取出索引表某个范围内的索引对应的数据，而不用像上述那取出所有数据进行排序再返回某个范围内的数据。（从磁盘取数据是最影响性能的）</p>
<p>Join</p>
<p>对Join语句匹配关系（on）涉及的字段建立索引能够提高效率</p>
<p>索引覆盖</p>
<p>如果要查询的字段都建立过索引，那么引擎会直接在索引表中查询而不会访问原始数据（否则只要有一个字段没有建立索引就会做全表扫描），这叫索引覆盖。因此我们需要尽可能的在Select后只写必要的查询字段，以增加索引覆盖的几率。</p>
<p>这里值得注意的是不要想着为每个字段建立索引，因为优先使用索引的优势就在于其体积小。</p>
<p><strong>四、索引有哪几种类型？</strong></p>
<p>1.主键索引: 数据列不允许重复，不允许为NULL，一个表只能有一个主键。</p>
<p>2.唯一索引: 数据列不允许重复，允许为NULL值，一个表允许多个列创建唯一索引。</p>
<p>可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建普通索引。</p>
<p>可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2)， 创建唯一组合索引。</p>
<p>3.普通索引: 基本的索引类型，没有唯一性的限制，允许为NULL值。</p>
<p>可以通过ALTER TABLE table_name ADD INDEX index_name (column)，创建普通索引。</p>
<p>可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3)，创建组合索引。</p>
<p>4.全文索引：是目前搜索引擎使用的一种关键技术。</p>
<p>可以通过ALTER TABLE table_name ADD FULLTEXT (column)，创建全文索引。</p>
<p><strong>五、索引的数据结构（B树，Hash）</strong></p>
<p>索引的数据结构和具体存储引擎的实现有关，在MySQL中使用较多的索引有Hash索引，B+树索引等，而我们经常使用的InnoDB存储引擎的默认索引实现为：B+树索引。</p>
<p>对于哈希索引来说，底层的数据结构就是哈希表，因此在绝大多数需求为单条记录查询的时候，可以选择哈希索引，查询性能最快；其余大部分场景，建议选择BTree索引。</p>
<p>1.B树索引</p>
<p>MySQL通过存储引擎取数据，基本上90%的人用的就是InnoDB了，按照实现方式分，InnoDB的索引类型目前只有两种：BTree（B树）索引和Hash索引。B树索引是MySQL数据库中使用最频繁的索引类型，基本所有存储引擎都支持BTree索引。通常我们说的索引不出意外指的就是（B树）索引（实际是用B+树实现的，因为在查看表索引时，MySQL一律打印BTree，所以简称为B树索引）</p>
<p><img src="http://iubest.gitee.io/pic/640-1601168450530.webp" alt="img"></p>
<p>查询方式：</p>
<p>主键索引区：PI(关联保存的数据的地址)按主键查询,</p>
<p>普通索引区：SI(关联的ID的地址，然后再到达上面的地址)。所以按主键查询,速度最快</p>
<p>B+tree性质：</p>
<p>1）n棵子tree的节点包含n个关键字，不用来保存数据而是保存数据的索引。</p>
<p>2）所有的叶子结点中包含了全部关键字的信息，及指向含这些关键字记录的指针，且叶子结点本身依关键字的大小自小而大顺序链接。</p>
<p>3）所有的非终端结点可以看成是索引部分，结点中仅含其子树中的最大（或最小）关键字。</p>
<p>4）B+ 树中，数据对象的插入和删除仅在叶节点上进行。</p>
<p>5）B+树有2个头指针，一个是树的根节点，一个是最小关键码的叶节点。</p>
<p>2.哈希索引</p>
<p>简要说下，类似于数据结构中简单实现的Hash表（散列表）一样，当我们在MySQL中用哈希索引时，主要就是通过Hash算法（常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法），将数据库字段数据转换成定的Hash值，与这条数据的行指针一并存入Hash表的对应位置；如果发生Hash碰撞（两个不同关键字的Hash值相同），则在对应Hash键下以链表形式存储。当然这只是简略模拟图。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601168481701.webp" alt="img"></p>
<p><strong>六、索引的基本原理</strong></p>
<p>索引用来快速地寻找那些具有特定值的记录。如果没有索引，一般来说执行查询时遍历整张表。</p>
<p>索引的原理很简单，就是把无序的数据变成有序的查询</p>
<p>1.把创建了索引的列的内容进行排序</p>
<p>2.对排序结果生成倒排表</p>
<p>3.在倒排表内容上拼上数据地址链</p>
<p>4.在查询的时候，先拿到倒排表内容，再取出数据地址链，从而拿到具体数据</p>
<p><strong>七、索引算法有哪些？</strong></p>
<p>索引算法有 BTree算法和Hash算法</p>
<p>BTree算法</p>
<p>BTree是最常用的MySQL数据库索引算法，也是MySQL默认的算法。因为它不仅可以被用在=,&gt;,&gt;=,&lt;,&lt;=和between这些比较操作符上，而且还可以用于LIKE操作符，只要它的查询条件是一个不以通配符开头的常量， 例如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">-- 只要它的查询条件是一个不以通配符开头的常量</span><br><span class="line">select * from user where name like &#39;jack%&#39;; </span><br><span class="line">-- 如果统一配符开头，或者没有使用常量，则不会使用索引，例如： </span><br><span class="line">select * from user where name like &#39;%jack&#39;; </span><br></pre></td></tr></table></figure>



<p>Hash算法</p>
<p>Hash索引只能用于对等比较，例如=,&lt;=&gt;（相当于=）操作符。由于是一次定位数据，不像BTree索引需要从根节点到枝节点，最后才能访问到页节点这样多次IO访问，所以检索效率远高于BTree索引。</p>
<h3 id="八、索引设计的原则？"><a href="#八、索引设计的原则？" class="headerlink" title="八、索引设计的原则？"></a><strong>八、索引设计的原则？</strong></h3><p>1.适合索引的列是出现在Where子句中的列，或者连接子句中指定的列。</p>
<p>2.基数较小的类，索引效果较差，没有必要在此列建立索引。</p>
<p>3.使用短索引，如果对长字符串列进行索引，应该指定一个前缀长度，这样能够节省大量索引空间。</p>
<p>4.不要过度索引，索引需要额外的磁盘空间，并降低写操作的性能。在修改表内容的时候，索引会进行更新甚至重构，索引列越多，这个时间就会越长。所以只保持需要的索引有利于查询即可。</p>
<h3 id="九、创建索引的原则（重中之重）"><a href="#九、创建索引的原则（重中之重）" class="headerlink" title="九、创建索引的原则（重中之重）"></a>九、创建索引的原则（重中之重）</h3><p>索引虽好，但也不是无限制的使用，最好符合以下几个原则</p>
<p>1.最左前缀匹配原则，组合索引非常重要的原则，MySQL会一直向右匹配直到遇到范围查询(&gt;、&lt;、between、like)就停止匹配，比如a = 1 and b = 2 and c &gt; 3 and d = 4 如果建立(a,b,c,d)顺序的索引，d是用不到索引的，如果建立(a,b,d,c)的索引则都可以用到，a,b,d的顺序可以任意调整。</p>
<p>2.较频繁作为查询条件的字段才去创建索引。</p>
<p>3.更新频繁字段不适合创建索引。</p>
<p>4.若是不能有效区分数据的列不适合做索引列(如性别，男女未知，最多也就三种，区分度实在太低)。</p>
<p>5.尽量的扩展索引，不要新建索引。比如表中已经有a的索引，现在要加(a,b)的索引，那么只需要修改原来的索引即可。</p>
<p>6.定义有外键的数据列一定要建立索引。</p>
<p>7.对于那些查询中很少涉及的列，重复值比较多的列不要建立索引。</p>
<p>8.对于定义为text、image和bit的数据类型的列不要建立索引。</p>
<h3 id="十、创建索引的三种方式，删除索引"><a href="#十、创建索引的三种方式，删除索引" class="headerlink" title="十、创建索引的三种方式，删除索引"></a>十、创建索引的三种方式，删除索引</h3><p>第一种方式：在执行CREATE TABLE时创建索引</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">CREATE TABLE user_index2 (</span><br><span class="line">    id INT auto_increment PRIMARY KEY,</span><br><span class="line">    first_name VARCHAR (16),</span><br><span class="line">    last_name VARCHAR (16),</span><br><span class="line">    id_card VARCHAR (18),</span><br><span class="line">    information text,</span><br><span class="line">    KEY name (first_name, last_name),</span><br><span class="line">    FULLTEXT KEY (information),</span><br><span class="line">    UNIQUE KEY (id_card)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>



<p>第二种方式：使用ALTER TABLE命令去增加索引</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TABLE table_name ADD INDEX index_name (column_list);</span><br></pre></td></tr></table></figure>



<p>ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。</p>
<p>其中table_name是要增加索引的表名，column_list指出对哪些列进行索引，多列时各列之间用逗号分隔。</p>
<p>索引名index_name可自己命名，缺省时，MySQL将根据第一个索引列赋一个名称。另外，ALTER TABLE允许在单个语句中更改多个表，因此可以在同时创建多个索引。</p>
<p>第三种方式：使用CREATE INDEX命令创建</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CREATE INDEX index_name ON table_name (column_list);</span><br></pre></td></tr></table></figure>



<p>CREATE INDEX可对表增加普通索引或UNIQUE索引。（但是，不能创建PRIMARY KEY索引）</p>
<p>删除索引</p>
<p>根据索引名删除普通索引、唯一索引、全文索引：<code>alter table 表名 drop KEY 索引名</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">alter table user_index drop KEY name;</span><br><span class="line">alter table user_index drop KEY id_card;</span><br><span class="line">alter table user_index drop KEY information;</span><br></pre></td></tr></table></figure>



<p>删除主键索引：<code>alter table 表名 drop primary key</code>（因为主键只有一个）。这里值得注意的是，如果主键自增长，那么不能直接执行此操作（自增长依赖于主键索引）：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601168450881.png" alt="img"></p>
<p>需要取消自增长再行删除：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">alter table user_index</span><br><span class="line">-- 重新定义字段</span><br><span class="line">MODIFY id int,</span><br><span class="line">drop PRIMARY KEY</span><br></pre></td></tr></table></figure>



<p>但通常不会删除主键，因为设计主键一定与业务逻辑无关。</p>
<h3 id="十一、创建索引时需要注意什么？"><a href="#十一、创建索引时需要注意什么？" class="headerlink" title="十一、创建索引时需要注意什么？"></a><strong>十一、创建索引时需要注意什么？</strong></h3><p>1.非空字段：应该指定列为NOT NULL，除非你想存储NULL。在MySQL中，含有空值的列很难进行查询优化，因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值；</p>
<p>2.取值离散大的字段：（变量各个取值之间的差异程度）的列放到联合索引的前面，可以通过count()函数查看字段的差异值，返回值越大说明字段的唯一值越多字段的离散程度高；</p>
<p>3.索引字段越小越好：数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。</p>
<p><strong>十二、使用索引查询一定能提高查询的性能吗？为什么？</strong></p>
<p>通常，通过索引查询数据比全表扫描要快，但是我们也必须注意到它的代价。</p>
<p>索引需要空间来存储，也需要定期维护， 每当有记录在表中增减或索引列被修改时，索引本身也会被修改。这意味着每条记录的INSERT，DELETE，UPDATE将为此多付出4，5 次的磁盘I/O。因为索引需要额外的存储空间和处理，那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能，索引范围查询(INDEX RANGE SCAN)适用于两种情况:</p>
<ul>
<li>基于一个范围的检索，一般查询返回结果集小于表中记录数的30%</li>
<li>基于非唯索引的检索</li>
</ul>
<h3 id="十三、百万级别或以上的数据如何删除"><a href="#十三、百万级别或以上的数据如何删除" class="headerlink" title="十三、百万级别或以上的数据如何删除"></a>十三、百万级别或以上的数据如何删除</h3><p>关于索引：由于索引需要额外的维护成本，因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO，会降低增/改/删的执行效率。</p>
<p>所以，在我们删除数据库百万级别数据的时候，查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。</p>
<p>1.所以我们想要删除百万数据的时候可以先删除索引（此时大概耗时三分多钟）。</p>
<p>2.然后删除其中无用数据（此过程需要不到两分钟）。</p>
<p>3.删除完成后重新创建索引(此时数据较少了)创建索引也非常快，约十分钟左右。</p>
<p>4.与之前的直接删除绝对是要快速很多，更别说万一删除中断,一切删除会回滚。那更是坑了。</p>
<h3 id="十四、前缀索引"><a href="#十四、前缀索引" class="headerlink" title="十四、前缀索引"></a>十四、前缀索引</h3><p>语法：<code>index(field(10))</code>，使用字段值的前10个字符建立索引，默认是使用字段的全部内容建立索引。</p>
<p>前提：前缀的标识度高。比如密码就适合建立前缀索引，因为密码几乎各不相同。</p>
<p>实操的难度：在于前缀截取的长度。</p>
<p>我们可以利用<code>select count(*)/count(distinct left(password,prefixLen));</code>，通过从调整<code>prefixLen</code>的值（从1自增）查看不同前缀长度的一个平均匹配度，接近1时就可以了（表示一个密码的前<code>prefixLen</code>个字符几乎能确定唯一一条记录）</p>
<h3 id="十五、什么是最左前缀原则？什么是最左匹配原则"><a href="#十五、什么是最左前缀原则？什么是最左匹配原则" class="headerlink" title="十五、什么是最左前缀原则？什么是最左匹配原则"></a><strong>十五、什么是最左前缀原则？什么是最左匹配原则</strong></h3><ul>
<li>顾名思义，就是最左优先，在创建多列索引时，要根据业务需求，where子句中使用最频繁的一列放在最左边。</li>
<li>最左前缀匹配原则，非常重要的原则，mysql会一直向右匹配直到遇到范围查询(&gt;、&lt;、between、like)就停止匹配，比如a = 1 and b = 2 and c &gt; 3 and d = 4 如果建立(a,b,c,d)顺序的索引，d是用不到索引的，如果建立(a,b,d,c)的索引则都可以用到，a,b,d的顺序可以任意调整。</li>
<li>=和in可以乱序，比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序，MySQL的查询优化器会帮你优化成索引可以识别的形式</li>
</ul>
<h3 id="十六、B树和B-树的区别"><a href="#十六、B树和B-树的区别" class="headerlink" title="十六、B树和B+树的区别"></a>十六、B树和B+树的区别</h3><ul>
<li>在B树中，你可以将键和值存放在内部节点和叶子节点；但在B+树中，内部节点都是键，没有值，叶子节点同时存放键和值。</li>
<li>B+树的叶子节点有一条链相连，而B树的叶子节点各自独立。</li>
</ul>
<p><img src="http://iubest.gitee.io/pic/640-1601168450324.webp" alt="img"></p>
<h3 id="十七、使用B树的好处"><a href="#十七、使用B树的好处" class="headerlink" title="十七、使用B树的好处"></a>十七、使用B树的好处</h3><p>B树可以在内部节点同时存储键和值，因此，把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。</p>
<h3 id="十八、使用B-树的好处"><a href="#十八、使用B-树的好处" class="headerlink" title="十八、使用B+树的好处"></a>十八、使用B+树的好处</h3><p>由于B+树的内部节点只存放键，不存放值，因此，一次读取，可以在内存页中获取更多的键，有利于更快地缩小查找范围。B+树的叶节点由一条链相连，因此，当需要进行一次全数据遍历的时候，B+树只需要使用O(logN)时间找到最小的一个节点，然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历，这会需要更多的内存置换次数，因此也就需要花费更多的时间</p>
<h3 id="十九、Hash索引和B-树所有有什么区别或者说优劣呢"><a href="#十九、Hash索引和B-树所有有什么区别或者说优劣呢" class="headerlink" title="十九、Hash索引和B+树所有有什么区别或者说优劣呢?"></a>十九、Hash索引和B+树所有有什么区别或者说优劣呢?</h3><p>首先要知道Hash索引和B+树索引的底层实现原理：</p>
<p>Hash索引底层就是Hash表，进行查找时，调用一次Hash函数就可以获取到相应的键值，之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发，查找到叶子节点方可以获得所查键值，然后根据查询判断是否需要回表查询数据。</p>
<p>那么可以看出他们有以下的不同：</p>
<ul>
<li>Hash索引进行等值查询更快(一般情况下)，但是却无法进行范围查询。因为在Hash索引中经过Hash函数建立索引之后，索引的顺序与原顺序无法保持一致，不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点，右节点大于父节点，多叉树也类似)，天然支持范围。</li>
<li>Hash索引不支持使用索引进行排序，原理同上。</li>
<li>Hash索引不支持模糊查询以及多列索引的最左前缀匹配。原理也是因为hash函数的不可预测。AAAA和AAAAB的索引没有相关性。</li>
<li>Hash索引任何时候都避免不了回表查询数据，而B+树在符合某些条件(聚簇索引，覆盖索引等)的时候可以只通过索引完成查询。</li>
<li>Hash索引虽然在等值查询上较快，但是不稳定。性能不可预测，当某个键值存在大量重复的时候，发生Hash碰撞，此时效率可能极差。而B+树的查询效率比较稳定，对于所有的查询都是从根节点到叶子节点，且树的高度较低。</li>
</ul>
<p>因此，在大多数情况下，直接选择B+树索引可以获得稳定且较好的查询速度。而不需要使用Hash索引。</p>
<h3 id="二十、数据库为什么使用B-树而不是B树"><a href="#二十、数据库为什么使用B-树而不是B树" class="headerlink" title="二十、数据库为什么使用B+树而不是B树"></a>二十、数据库为什么使用B+树而不是B树</h3><ul>
<li>B树只适合随机检索，而B+树同时支持随机检索和顺序检索；</li>
<li>B+树空间利用率更高，可减少I/O次数，磁盘读写代价更低。一般来说，索引本身也很大，不可能全部存储在内存中，因此索引往往以索引文件的形式存储的磁盘上。这样的话，索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针，只是作为索引使用，其内部结点比B树小，盘块能容纳的结点中关键字数量更多，一次性读入内存中可以查找的关键字也就越多，相对的，IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素；</li>
<li>B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束，越靠近根节点的记录查找时间越短，只要找到关键字即可确定记录的存在，其性能等价于在关键字全集内做一次二分查找。而在B+树中，顺序检索比较明显，随机检索时，任何关键字的查找都必须走一条从根节点到叶节点的路，所有关键字的查找路径长度相同，导致每一个关键字的查询效率相当。</li>
<li>B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起，只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的，而B树不支持这样的操作。</li>
<li>增删文件（节点）时，效率更高。因为B+树的叶子节点包含所有关键字，并以有序的链表结构存储，这样可很好提高增删效率。</li>
</ul>
<h3 id="二十一、B-树在满足聚簇索引和覆盖索引的时候不需要回表查询数据"><a href="#二十一、B-树在满足聚簇索引和覆盖索引的时候不需要回表查询数据" class="headerlink" title="二十一、B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据"></a>二十一、B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据</h3><p>在B+树的索引中，叶子节点可能存储了当前的Key值，也可能存储了当前的Key值以及整行的数据，这就是聚簇索引和非聚簇索引。在InnoDB中，只有主键索引是聚簇索引，如果没有主键，则挑选一个唯一键建立聚簇索引。如果没有唯一键，则隐式的生成一个键来建立聚簇索引。</p>
<p>当查询使用聚簇索引时，在对应的叶子节点，可以获取到整行数据，因此不用再次进行回表查询。</p>
<h3 id="二十二、什么是聚簇索引？何时使用聚簇索引与非聚簇索引"><a href="#二十二、什么是聚簇索引？何时使用聚簇索引与非聚簇索引" class="headerlink" title="二十二、什么是聚簇索引？何时使用聚簇索引与非聚簇索引"></a>二十二、什么是聚簇索引？何时使用聚簇索引与非聚簇索引</h3><ul>
<li><p>聚簇索引：将数据存储与索引放到了一块，找到索引也就找到了数据</p>
</li>
<li><p>非聚簇索引：将数据存储于索引分开结构，索引结构的叶子节点指向了数据的对应行，myisam通过key_buffer把索引先缓存到内存中，当需要访问数据时（通过索引访问数据），在内存中直接搜索索引，然后通过索引找到磁盘相应数据，这也就是为什么索引不在key buffer命中时，速度慢的原因。</p>
</li>
</ul>
<p>澄清一个概念：Innodb中，在聚簇索引之上创建的索引称之为辅助索引，辅助索引访问数据总是需要二次查找，非聚簇索引都是辅助索引，像复合索引、前缀索引、唯一索引，辅助索引子节点存储的不再是行的物理位置，而是主键值</p>
<p>何时使用聚簇索引与非聚簇索引</p>
<p><img src="http://iubest.gitee.io/pic/640-1601168450961.png" alt="img"></p>
<h3 id="二十三、非聚簇索引一定会回表查询吗？"><a href="#二十三、非聚簇索引一定会回表查询吗？" class="headerlink" title="二十三、非聚簇索引一定会回表查询吗？"></a>二十三、非聚簇索引一定会回表查询吗？</h3><p>不一定，这涉及到查询语句所要求的字段是否全部命中了索引，如果全部命中了索引，那么就不必再进行回表查询。</p>
<p>举个简单的例子，假设我们在员工表的年龄上建立了索引，那么当进行<code>select age from employee where age &lt; 20</code>的查询时，在索引的叶子节点上，已经包含了age信息，不会再次进行回表查询。</p>
<h3 id="二十四、联合索引是什么？为什么需要注意联合索引中的顺序？"><a href="#二十四、联合索引是什么？为什么需要注意联合索引中的顺序？" class="headerlink" title="二十四、联合索引是什么？为什么需要注意联合索引中的顺序？"></a><strong>二十四、联合索引是什么？为什么需要注意联合索引中的顺序？</strong></h3><p>MySQL可以使用多个字段同时建立一个索引，叫做联合索引。在联合索引中，如果想要命中索引，需要按照建立索引时的字段顺序挨个使用，否则无法命中索引。</p>
<p>具体原因为：</p>
<p>MySQL使用索引时需要索引有序，假设现在建立了”name，age，school”的联合索引，那么索引的排序为: 先按照name排序，如果name相同，则按照age排序，如果age的值也相等，则按照school进行排序。</p>
<p>当进行查询时，此时索引仅仅按照name严格有序，因此必须首先使用name字段进行等值查询，之后对于匹配到的列而言，其按照age字段严格有序，此时可以使用age字段用做索引查找，以此类推。因此在建立联合索引的时候应该注意索引列的顺序，一般情况下，将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mysql/" rel="tag">mysql</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-mysql/分享二十个mysql运维案例"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/mysql/%E5%88%86%E4%BA%AB%E4%BA%8C%E5%8D%81%E4%B8%AAmysql%E8%BF%90%E7%BB%B4%E6%A1%88%E4%BE%8B/"
    >分享二十个mysql运维案例.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/mysql/%E5%88%86%E4%BA%AB%E4%BA%8C%E5%8D%81%E4%B8%AAmysql%E8%BF%90%E7%BB%B4%E6%A1%88%E4%BE%8B/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/mysql/">mysql</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="分享二十个mysql运维案例"><a href="#分享二十个mysql运维案例" class="headerlink" title="分享二十个mysql运维案例"></a>分享二十个mysql运维案例</h1><p>日常mysql运维中，会遇到各种各样的问题，下面分享二十个mysql运维案例，附有问题的分析和解决办法，希望你遇到同样的问题的时候，可以淡定地处理。</p>
<h3 id="一-执行存储过程-函数报错账号不存在"><a href="#一-执行存储过程-函数报错账号不存在" class="headerlink" title="一.执行存储过程/函数报错账号不存在"></a>一.执行存储过程/函数报错账号不存在</h3><p>问题描述：执行存储过程报错<br><code>mysql&gt;call create_no_by_day(&#39;STUDENT&#39;,&#39;CREATE_TIME&#39;);</code><br><code>ERROR 1449 (HY000):The user specified as a definer (&#39;TEST_111&#39;@&#39;172.%.%.%&#39;) does not exist</code></p>
<p>分析：这情况是因为当时机器上这存储过程是由用户’TEST_111’@’172.%.%.%‘创建，但是存储过程导入到了新的机器后，这新机器没有这个用户而报错。<br>三种解决办法：<br>1、重建这个存储过程，把definer那段代码取消。<br>2、在新机器上建立这个用户’TEST_111’@‘172.%.%.%’。<br>3、修改定义者，替换所有存储过程是这个定义者的为新机器已有账号。</p>
<p>解决：修改定义者<br>1.查看机器不存在这个账号<br><code>select host,user from mysql.user where host=&#39;172.%.%.%&#39; and user=&#39;TEST_111&#39;;</code></p>
<p>2.替换所有存储过程是这个定义者的为新机器已有账号。<br><code>UPDATE  mysql.proc set DEFINER=&#39;TEST_222@172.%.%.%&#39; where DEFINER=&#39;TEST_111@172.%.%.%&#39;;</code></p>
<h3 id="二-遇到DDL变更的时候发生阻塞"><a href="#二-遇到DDL变更的时候发生阻塞" class="headerlink" title="二.遇到DDL变更的时候发生阻塞"></a>二.遇到DDL变更的时候发生阻塞</h3><p>问题描述：添加字段、添加索引等DDL语句时候会被阻塞，show processlist 会看到显示 Waiting for table metadata lock. 后续的对这些表的select也会被阻塞<br>分析：autocommit=0，怀疑有未提交事务，导致产生了元数据锁。<br>解决：<br>1.如果打开了performance_schema 监控，通过语句定位未提交事务：<br>SELECT<br>locked_schema,<br>locked_table,<br>locked_type,<br>waiting_processlist_id,<br>waiting_age,<br>waiting_query,<br>waiting_state,<br>blocking_processlist_id,<br>blocking_age,<br>substring_index(sql_text,“transaction_begin;” ,-1) AS blocking_query,<br>sql_kill_blocking_connection<br>FROM<br>(<br>SELECT<br>b.OWNER_THREAD_ID AS granted_thread_id,<br>a.OBJECT_SCHEMA AS locked_schema,<br>a.OBJECT_NAME AS locked_table,<br>“Metadata Lock” AS locked_type,<br>c.PROCESSLIST_ID AS waiting_processlist_id,<br>c.PROCESSLIST_TIME AS waiting_age,<br>c.PROCESSLIST_INFO AS waiting_query,<br>c.PROCESSLIST_STATE AS waiting_state,<br>d.PROCESSLIST_ID AS blocking_processlist_id,<br>d.PROCESSLIST_TIME AS blocking_age,<br>d.PROCESSLIST_INFO AS blocking_query,<br>concat(‘KILL ‘, d.PROCESSLIST_ID) AS sql_kill_blocking_connection<br>FROM<br>performance_schema.metadata_locks a<br>JOIN performance_schema.metadata_locks b ON a.OBJECT_SCHEMA = b.OBJECT_SCHEMA<br>AND a.OBJECT_NAME = b.OBJECT_NAME<br>AND a.lock_status = ‘PENDING’<br>AND b.lock_status = ‘GRANTED’<br>AND a.OWNER_THREAD_ID &lt;&gt; b.OWNER_THREAD_ID<br>AND a.lock_type = ‘EXCLUSIVE’<br>JOIN performance_schema.threads c ON a.OWNER_THREAD_ID = c.THREAD_ID<br>JOIN performance_schema.threads d ON b.OWNER_THREAD_ID = d.THREAD_ID<br>) t1,<br>(<br>SELECT<br>thread_id,<br>group_concat( CASE WHEN EVENT_NAME = ‘statement/sql/begin’ THEN “transaction_begin” ELSE sql_text END ORDER BY event_id SEPARATOR “;” ) AS sql_text<br>FROM<br>performance_schema.events_statements_history<br>GROUP BY thread_id<br>) t2<br>WHERE<br>t1.granted_thread_id = t2.thread_id</p>
<p>2.杀进程kill blocking_processlist_id</p>
<p>3.没有打开wait/lock/metadata/sql/mdl情况下，针对sleep进程执行kill<br>SELECT concat(‘kill ‘,processlist_id,’;’)<br>FROM performance_schema.events_statements_current a JOIN performance_schema.threads b USING(thread_id)<br>JOIN information_schema.processlist c ON b.processlist_id = c.id<br>WHERE a.sql_text NOT LIKE ‘%performance%’ and command=‘sleep’ order by c.time desc;</p>
<h3 id="三-批量更新数据卡住"><a href="#三-批量更新数据卡住" class="headerlink" title="三.批量更新数据卡住"></a>三.批量更新数据卡住</h3><p>问题描述：批量更新很慢，没添加索引，添加索引被阻塞<br>分析：事务不自动提交容易造成元数据锁冲突<br>解决：执行SELECT concat(‘kill ‘,processlist_id,’;’)<br>FROM performance_schema.events_statements_current a JOIN performance_schema.threads b USING(thread_id)<br>JOIN information_schema.processlist c ON b.processlist_id = c.id<br>WHERE a.sql_text NOT LIKE ‘%performance%’ and command=‘sleep’ order by c.time desc;<br>杀进程</p>
<h3 id="四-一个环境数据库占用空间满情况"><a href="#四-一个环境数据库占用空间满情况" class="headerlink" title="四.一个环境数据库占用空间满情况"></a>四.一个环境数据库占用空间满情况</h3><p>问题描述：一个环境数据库空间已满。<br>分析：数据没有定时清理，没有监控报警。一个保存实时消息的大表上百亿数据占用过大<br>问题解决：1.能登陆mysql情况下，truncate table 大表(无用数据，可清除)，回收空间<br>2.不能登陆mysql情况下，删除部分binlog日志，让mysql启动起来，再清理其他数据。</p>
<h3 id="五-数据库迁移"><a href="#五-数据库迁移" class="headerlink" title="五.数据库迁移"></a>五.数据库迁移</h3><p>问题描述：已备份了数据，想把数据库导进另一个环境，但是想换一个数据库名字，以免把另一个环境的同名数据库覆盖<br>问题解决：<br>1.导出数据<br><code># mysqldump -uroot -pxxx_001 --set-gtid-purged=OFF admin &gt; admin.sql</code></p>
<p>2． 建新库<br><code>mysql&gt;create database admin1;</code></p>
<p>3． 建立账号，授权<br><code>mysql&gt;CREATE USER admin1@&#39;%&#39; identified by &#39;Admin1_!@#&#39;;</code><br><code>mysql&gt;GRANT ALL PRIVILEGES ON  admin1.* TO admin1@&#39;%&#39;;</code><br><code>mysql&gt;flush privileges;</code></p>
<p>4． 导入数据到新库admin1<br><code># mysql -uroot -pxxx_001 admin1 &lt;admin.sql</code></p>
<p>这样，就把原库admin导出的表数据，导入了新库admin1里面。新账号admin1已授权访问新库admin1,新账号密码是Admin1_!@#</p>
<p>注： 操作2,3是sql语句，在mysql里面执行。 操作1,4是在linux命令行上操作。</p>
<h3 id="六-数据库日志出现多个断连记录"><a href="#六-数据库日志出现多个断连记录" class="headerlink" title="六.数据库日志出现多个断连记录"></a>六.数据库日志出现多个断连记录</h3><p>问题描述：数据库日志出现多个断连记录，显示为Got an error writing communication packets<br>分析：有可能是客户端异常退出了，应用重启，也有可能是网络链路异常。这种提示一般是[NOTE],属于提示信息。<br>问题解决：<br>关闭警报<br>set global log_warning=1;<br>另：若是出现Got timeout reading communication packets或者Got timeout writing communication packets，属于客户端的空连接时间过长，超过了wait_timeout和interactive_timeout的时间，可以调整wait_timeout/interactive_timeout</p>
<h3 id="七-非法断电造成mysql启动报错"><a href="#七-非法断电造成mysql启动报错" class="headerlink" title="七.非法断电造成mysql启动报错"></a>七.非法断电造成mysql启动报错</h3><p>问题描述：非法断电造成mysql数据损坏<br>分析：突然断电造成缓存数据丢失，跟已刷盘的数据不一致。需要重做从库。<br>问题解决：到主库物理备份数据，恢复到从库，恢复主从同步。<br>物理备份恢复过程：<br>主库:<br>mysqlbackup –user=root –password=xxx_001 –backup-dir=/mysql/data/backup –backup-image=./dball.mbi –with-timestamp –compress-level=9 –compress-method=zlib –skip-binlog –skip-relaylog backup-to-image #备份数据<br>scp dball.mbi <a href="mailto:&#x72;&#111;&#111;&#116;&#x40;&#x31;&#x39;&#x32;&#46;&#x31;&#x36;&#x38;&#x2e;&#x31;&#51;&#55;&#x2e;&#x31;&#x31;&#49;">&#x72;&#111;&#111;&#116;&#x40;&#x31;&#x39;&#x32;&#46;&#x31;&#x36;&#x38;&#x2e;&#x31;&#51;&#55;&#x2e;&#x31;&#x31;&#49;</a>:/mysql/data/backup/ #拷贝到目标机器backup目录</p>
<p>从库:<br>cd /mysql/data/backup/<br>chown mysql.mysql dball.mbi<br>su - mysql<br>mysql.server stop<br>cd /mysql/data/undo<br>rm -rf * #清空undo日志<br>cd /mysql/data/redo<br>rm -rf * #清空redo日志<br>cd /mysql/data/dbs<br>rm -rf * #清空数据<br>mysqlbackup –backup-image=/mysql/data/backup/dball.mbi –backup-dir=/mysql/data/backup –uncompress copy-back-and-apply-log –force #恢复数据<br>mysql.server start #启动mysql<br>mysql -uroot -p<br>reset slave all; #重置<br>change master to master_host = ‘192.168.137.110’, master_port = 3310, master_user = ‘rpl_user’, master_password = ‘rpl_001’, master_auto_position=1 ;#设置主从同步复制<br>start slave;#启动同步<br>show slave status\G;#查看同步状况</p>
<h3 id="八-非法断电造成mysql同步复制无法启动"><a href="#八-非法断电造成mysql同步复制无法启动" class="headerlink" title="八.非法断电造成mysql同步复制无法启动"></a>八.非法断电造成mysql同步复制无法启动</h3><p>问题描述：relay报错，error:look foring afer relay.000013<br>分析：relay得到的gtid比执行的gtid少，得到的部分gtid丢失。<br>show slave status\G;<br>Master_UUID: 37be0d7b-e11e-11e9-bafb-fa163e9dcbee<br>Retrieved_Gtid_Set: 37be0d7b-e11e-11e9-bafb-fa163e9dcbee:2290-2302(少)<br>Executed_Gtid_Set: 37be0d7b-e11e-11e9-bafb-fa163e9dcbee:1-2352(大),<br>5324e653-f0c2-11e9-84d0-fa163e897a41:1-363,<br>5661dce0-e11e-11e9-ab09-fa163e897a41:1-318</p>
<p>问题解决：<br>调整gtid，以relay获得的gtid为准，重做主从同步<br>reset slave all;#重置<br>reset master;#重置<br>SET @@GLOBAL.GTID_PURGED=‘37be0d7b-e11e-11e9-bafb-fa163e9dcbee:1-2302(少),5324e653-f0c2-11e9-84d0-fa163e897a41:1-363,5661dce0-e11e-11e9-ab09-fa163e897a41:1-318’;#以relay获得的gtid为准，设置GTID_PURGED<br>change master to master_host = ‘192.168.137.110’, master_port = 3310, master_user = ‘rpl_user’, master_password = ‘rpl_001’, master_auto_position=1 ;#设置主从同步复制<br>start slave; #启动同步<br>show slave status\G;#查看同步状况</p>
<h3 id="九-mysql压力测试，插入时间增大，压不上去"><a href="#九-mysql压力测试，插入时间增大，压不上去" class="headerlink" title="九.mysql压力测试，插入时间增大，压不上去"></a>九.mysql压力测试，插入时间增大，压不上去</h3><p>问题描述：压力测试，数据库插入出现延时情况。<br>分析：沟通发现，mysql是个人安装，非标准化，设置不当，例如32G的内存，innodb_buffer_pool_size只默认128M。<br>问题解决：<br>1.动态在线增大innodb_buffer_pool_size值。<br>set global innodb_buffer_pool_size=16<em>1024</em>1024*1024;</p>
<p>2.为了永久生效，在my.cnf里面设置<br>innodb_buffer_pool_size=16G<br>innodb_buffer_pool_instances = 8<br>重启mysql</p>
<h3 id="十-通过恢复文件加载空间恢复表数据"><a href="#十-通过恢复文件加载空间恢复表数据" class="headerlink" title="十.通过恢复文件加载空间恢复表数据"></a>十.通过恢复文件加载空间恢复表数据</h3><p>问题描述：出现测试环境数据库ibdata1损坏，无法启动。<br>分析：通过开发环境的mysql全量备份，恢复到测试环境。由于开发环境的admin库数据库结构跟测试环境的admin库一样，但是数据不一样，需要用原测试环境数据文件恢复原表数据。<br>问题解决：<br>1.备份数据<br>mkdir backup<br>cp dbs/admin/* backup/<br>2.开发环境mysqlbackup全量备份恢复到测试环境<br>3.卸载库admin所有表空间<br>mysql -uroot -p -e “select concat(‘alter table ‘,table_name,’ DISCARD tablespace;’) from information_schema.TABLES where table_schema=‘admin’ and table_type=‘BASE TABLE’;” &gt;admin_discard.sql<br>vi admin_discard.sql<br>删除第一行concat(‘alter table ‘,table_name,’ DISCARD tablespace;’)<br>mysql -uroot -p admin &lt;admin_discard.sql<br>4.拷贝回.ibd文件<br>cp -f backup/*.ibd dbs/admin/<br>5.加载库admin所有表空间<br>mysql -uroot -p -e “select concat(‘alter table ‘,table_name,’ IMPORT tablespace;’) from information_schema.TABLES where table_schema=‘admin’ and table_type=‘BASE TABLE’;” &gt;admin_import.sql<br>vi admin_import.sql<br>删除第一行concat(‘alter table ‘,table_name,’ IMPORT tablespace;’)<br>mysql -uroot -p admin &lt;admin_import.sql<br>6.启动mysql<br>mysql.server start</p>
<h3 id="十一-XA事务未提交，更新数据超时"><a href="#十一-XA事务未提交，更新数据超时" class="headerlink" title="十一.XA事务未提交，更新数据超时"></a>十一.XA事务未提交，更新数据超时</h3><p>问题描述：出现更新数据超时问题<br>分析：查看事务进程，发现有未提交的xa事务，thread_id为0.<br>问题解决：</p>
<p>1.先列出xa事务<br>mysql&gt; xa recover;<br>±———±————-±————-±———————————————————————————————————————————+<br>| formatID | gtrid_length | bqual_length | data |<br>±———±————-±————-±———————————————————————————————————————————+<br>| 3072 | 64 | 64 | PRBMPAPP4/c734a63e-211f-4202-8aa9-78d2/174064445 3 |<br>±———±————-±————-±———————————————————————————————————————————+<br>1 row in set (0.10 sec)<br>xa回滚的格式是xa rollback ‘left(data,gtrid_length)’,‘substr(data,gtrid_length+1,bqual_length)’,formatid;<br>2.转16制<br>mysql&gt; set session autocommit=1;<br>Query OK, 0 rows affected (0.00 sec)</p>
<p>mysql&gt; xa recover convert xid;<br>±———±————-±————-±——————————————————————————————————————————————————————————————————————————————————————-+<br>| formatID | gtrid_length | bqual_length | data |<br>±———±————-±————-±——————————————————————————————————————————————————————————————————————————————————————-+<br>| 3072 | 64 | 64 | 0x5052424D50415050342F63373334613633652D323131662D343230322D386161392D373864322F3137343036343434350000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |<br>±———±————-±————-±——————————————————————————————————————————————————————————————————————————————————————-+<br>1 row in set (0.00 sec)<br>3.回滚事务<br>mysql&gt; xa rollback 0x5052424D50415050342F63373334613633652D323131662D343230322D386161392D373864322F31373430363434343500000000000000000000000000000000,0x33000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,3072;<br>Query OK, 0 rows affected (0.01 sec)</p>
<h3 id="十二-mysql升级5-7-26，更换驱动后原账号连不上"><a href="#十二-mysql升级5-7-26，更换驱动后原账号连不上" class="headerlink" title="十二.mysql升级5.7.26，更换驱动后原账号连不上"></a>十二.mysql升级5.7.26，更换驱动后原账号连不上</h3><p>问题描述：mysql从5.7.24升级到mysql5.7.26，驱动从mysql connector java更换为mariadb，业务升级前就是这个密码，运行很久，有3套mysql，有1套重启数据库还不能恢复，有2套重启数据库恢复了<br>分析：mariadb驱动对sha256_password识别不好，改用低版本mysql_native_password<br>问题解决：<br>1.查询当前密码加密方式<br>SELECT user, host, plugin FROM mysql.user;<br>mysql&gt; SELECT user, host, plugin FROM mysql.user;<br>±————–±————–±———————-+<br>| user | host | plugin |<br>±————–±————–±———————-+<br>| root | % | sha256_password |<br>| mysql.session | localhost | mysql_native_password |<br>| mysql.sys | localhost | mysql_native_password |<br>| mysql_om | % | sha256_password |<br>| rpl_user | 10.110.10.19 | sha256_password |<br>| root | 10.110.10.156 | sha256_password |<br>| xxxadmin | % | sha256_password |<br>| xxxuser | % | sha256_password |<br>| mysql | % | sha256_password |<br>±————–±————–±———————-+<br>14 rows in set (0.00 sec)<br>2.修改加密方式<br>ALTER USER ‘xxxuser’@’%’ IDENTIFIED WITH mysql_native_password BY ‘XXX_1234’;<br>flush privileges;</p>
<h3 id="十三-mysql无法启动，数据目录丢失"><a href="#十三-mysql无法启动，数据目录丢失" class="headerlink" title="十三.mysql无法启动，数据目录丢失"></a>十三.mysql无法启动，数据目录丢失</h3><p>问题描述： mysql无法启动，查看error日志，提示mysql.user不存在，查看my.cnf数据所在目录，发现目录里面除了ibdata1和ib_logfile，auto.cnf，没有其他数据。<br>分析：挂载mysql数据库目录丢失。mount的盘信息没写入fstab里面，断电重启后，没有挂载磁盘。<br>问题解决：<br>1.挂载数据库目录<br>mount -t ext3 /dev/sda3 /mysqldata<br>2.启动mysql<br>service mysql start</p>
<h3 id="十四-无法登陆mysql，can’t-connect。。。。mysql-sock"><a href="#十四-无法登陆mysql，can’t-connect。。。。mysql-sock" class="headerlink" title="十四.无法登陆mysql，can’t connect。。。。mysql.sock"></a>十四.无法登陆mysql，can’t connect。。。。mysql.sock</h3><p>问题描述：can’t connect 。。。。。。’/mysqldata/mysql/mysql.sock’<br>分析：在数据目录找不到mysql.sock文件或者mysql没有启动<br>问题解决：<br>1.找不到mysql.sock文件<br>登录加上-S 参数 或者ln -s 建立软连或者在my.cnf里面写上<br>mysql -uroot -p -S /usr/mysql.sock<br>或者 ln -s /usr/mysql.sock /mysqldata/mysql/mysql.sock<br>2.mysql没有启动<br>mysql.server start</p>
<h3 id="十五-数据目录存在非法目录"><a href="#十五-数据目录存在非法目录" class="headerlink" title="十五.数据目录存在非法目录"></a>十五.数据目录存在非法目录</h3><p>问题描述：错误日志显示mysql error invalid (old ) table or database name ‘lost+found’<br>分析：datadir里面的一个目录代表一个数据库，非法写入目录，导致mysql不能识别<br>问题解决：<br>删除目录’lost+found’。</p>
<h3 id="十六-错误日志报警告信息长度不匹配"><a href="#十六-错误日志报警告信息长度不匹配" class="headerlink" title="十六.错误日志报警告信息长度不匹配"></a>十六.错误日志报警告信息长度不匹配</h3><p>问题描述：[Warning] InnoDB: Table mysql/innodb_table_stats has length mismatch in the column name table_name. Please run mysql_upgrade<br>分析：5.7.17：table_name varchar(64) COLLATE utf8_bin NOT NULL,<br>5.7.24：table_name varchar(199) COLLATE utf8_bin NOT NULL,<br>可以看出，5.7.24 版本上的 innodb_index_stats 和innodb_tables_stats 的 table_name 列，长度从64 变成了 199，而升级后的5.7.24中 table_name 还是64</p>
<p>问题解决：<br>1.执行更新<br>mysql_upgrade -u root -p –force<br>2.重启mysqld进程<br>mysql.server start</p>
<h3 id="十七-错误日志报警告信息ignored-in-–skip-name-resolve-mode"><a href="#十七-错误日志报警告信息ignored-in-–skip-name-resolve-mode" class="headerlink" title="十七.错误日志报警告信息ignored in –skip-name-resolve mode"></a>十七.错误日志报警告信息ignored in –skip-name-resolve mode</h3><p>问题描述：[Warning] ‘user’ entry ‘mysql.session@localhost’ ignored in –skip-name-resolve mode.<br>分析：跳过dns解析，打开了skip_name_resolve后出现<br>问题解决：<br>删除不必要的账号<br>delete from mysql.user where user=‘mysql.session’</p>
<h3 id="十八-大量Waiting-in-connection-control-plugin连接"><a href="#十八-大量Waiting-in-connection-control-plugin连接" class="headerlink" title="十八.大量Waiting in connection_control plugin连接"></a>十八.大量Waiting in connection_control plugin连接</h3><p>问题描述:show processlist;结果超过2000条结果. 90%连接:Waiting in connection_control plugin<br>分析：启动了防爆破插件connection_control<br>问题解决:<br>show plugins;<br>select * from information_schema.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS;<br>1.修改参数show variables like ‘%connection_control%’;<br>set global connection_control_failed_connections_threshold=0;<br>set global connection_control_max_connection_delay=3000;</p>
<p>或者2.重启mysql服务<br>mysql.server restart</p>
<p>3.kill进程<br>select concat(‘kill ‘, id, ‘;’) from information_schema.processlist where command= ‘Waiting in connection_control plugin’;</p>
<h3 id="十九-备库同步复制通道channel值为空"><a href="#十九-备库同步复制通道channel值为空" class="headerlink" title="十九.备库同步复制通道channel值为空"></a>十九.备库同步复制通道channel值为空</h3><p>现象描述：多源复制，同步复制通道channel值为空<br>问题分析：需要重置复制通道<br>问题解决:<br>1.停止slave<br>stop slave;<br>2.清空同步复制关联<br>reset slave all;<br>3.重置同步复制关联<br>change master to master_host = ‘192.168.137.110’, master_port = 3310, master_user = ‘rpl_user’, master_password = ‘xxxxxxxxxx’, master_auto_position=1 for channel ‘rpl1’;<br>4.启动slave<br>start slave;</p>
<p>另外，多源同步复制通道的知识点，官网和互联网上都很少，以下是经过本人实践的：<br>1.多源复制，同步复制通道channel修改。<br>1）update update mysql.slave_master_info set channel_name=“新通道名”;<br>2）service mysqld restart<br>3）show slave status\G; #这时候会多一条通道channel，旧通道和新通道<br>4）stop slave for channel “旧通道名”;<br>5）reset slave all for channel “旧通道名”;</p>
<p>2.多源复制，同步复制通道channel多一条，删除<br>1）stop slave for channel “删除通道名”;<br>2）reset slave all for channel “删除通道名”;#若是通道名为空，值””.<br>3）show slave status\G;</p>
<h3 id="二十-mysql时区跟系统数据不一致"><a href="#二十-mysql时区跟系统数据不一致" class="headerlink" title="二十.mysql时区跟系统数据不一致"></a>二十.mysql时区跟系统数据不一致</h3><p>问题描述：mysql&gt; select now();<br>±——————–+<br>| now() |<br>±——————–+<br>| 2020-05-27 06:27:24 |<br>±——————–+<br>1 row in set (0.00 sec)<br>mysql&gt; show global variables like ‘%time_zone%’;<br>±—————–±——-+<br>| Variable_name | Value |<br>±—————–±——-+<br>| system_time_zone | UTC |<br>| time_zone | SYSTEM |<br>±—————–±——-+<br>2 rows in set (0.01 sec)<br>mysql@tb6usrdb2:~&gt; date<br>Wed May 27 09:27:34 CEST 2020<br>分析：系统是CEST时区，与mysql的时区不一致，差3个小时<br>问题解决：<br>1.修改mysql时区：<br>set time_zone=’+2:00’; #+2,不是+3</p>
<p>2.结果：<br>mysql&gt; show global variables like ‘%time_zone%’;<br>±—————–±——-+<br>| Variable_name | Value |<br>±—————–±——-+<br>| system_time_zone | UTC |<br>| time_zone | +02:00 |<br>±—————–±——-+<br>2 rows in set (0.00 sec)</p>
<p>若是要永久生效就是在my.cnf中修改，添加default-time_zone=’+2:00’<br>然后重启mysql。<br>mysql&gt; show global variables like ‘%time_zone%’;<br>±—————–±——-+<br>| Variable_name | Value |<br>±—————–±——-+<br>| system_time_zone | CEST |<br>| time_zone | +02:00 |<br>±—————–±——-+<br>2 rows in set (0.00 sec)</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/mysql/" rel="tag">mysql</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-other/原来 8 张图，就可以搞懂「零拷贝」了"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/other/%E5%8E%9F%E6%9D%A5%208%20%E5%BC%A0%E5%9B%BE%EF%BC%8C%E5%B0%B1%E5%8F%AF%E4%BB%A5%E6%90%9E%E6%87%82%E3%80%8C%E9%9B%B6%E6%8B%B7%E8%B4%9D%E3%80%8D%E4%BA%86/"
    >原来 8 张图，就可以搞懂「零拷贝」了.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/other/%E5%8E%9F%E6%9D%A5%208%20%E5%BC%A0%E5%9B%BE%EF%BC%8C%E5%B0%B1%E5%8F%AF%E4%BB%A5%E6%90%9E%E6%87%82%E3%80%8C%E9%9B%B6%E6%8B%B7%E8%B4%9D%E3%80%8D%E4%BA%86/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/other/">other</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="原来-8-张图，就可以搞懂「零拷贝」了"><a href="#原来-8-张图，就可以搞懂「零拷贝」了" class="headerlink" title="原来 8 张图，就可以搞懂「零拷贝」了"></a>原来 8 张图，就可以搞懂「零拷贝」了</h2><p><a href="javascript:void(0);">我没有三颗心脏</a> <em>前天</em></p>
<p>以下文章来源于小林coding ，作者小林coding</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001235076.webp" alt="img"></p>
<hr>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>磁盘可以说是计算机系统最慢的硬件之一，读写速度相差内存 10 倍以上，所以针对优化磁盘的技术非常的多，比如零拷贝、直接 I/O、异步 I/O 等等，这些优化的目的就是为了提高系统的吞吐量，另外操作系统内核中的磁盘高速缓存区，可以有效的减少磁盘的访问次数。</p>
<p>这次，我们就以「文件传输」作为切入点，来分析 I/O 工作方式，以及如何优化传输文件的性能。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001233923.webp" alt="img"></p>
<hr>
<h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><h3 id="为什么要有-DMA-技术"><a href="#为什么要有-DMA-技术" class="headerlink" title="为什么要有 DMA 技术?"></a>为什么要有 DMA 技术?</h3><p>在没有 DMA 技术前，I/O 的过程是这样的：</p>
<ul>
<li>CPU 发出对应的指令给磁盘控制器，然后返回；</li>
<li>磁盘控制器收到指令后，于是就开始准备数据，会把数据放入到磁盘控制器的内部缓冲区中，然后产生一个<strong>中断</strong>；</li>
<li>CPU 收到中断信号后，停下手头的工作，接着把磁盘控制器的缓冲区的数据一次一个字节地读进自己的寄存器，然后再把寄存器里的数据写入到内存，而在数据传输的期间 CPU 是无法执行其他任务的。</li>
</ul>
<p>为了方便你理解，我画了一副图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234020.webp" alt="img"></p>
<p>可以看到，整个数据的传输过程，都要需要 CPU 亲自参与搬运数据的过程，而且这个过程，CPU 是不能做其他事情的。</p>
<p>简单的搬运几个字符数据那没问题，但是如果我们用千兆网卡或者硬盘传输大量数据的时候，都用 CPU 来搬运的话，肯定忙不过来。</p>
<p>计算机科学家们发现了事情的严重性后，于是就发明了 DMA 技术，也就是<strong>直接内存访问（*Direct Memory Access*）</strong> 技术。</p>
<p>什么是 DMA 技术？简单理解就是，<strong>在进行 I/O 设备和内存的数据传输的时候，数据搬运的工作全部交给 DMA 控制器，而 CPU 不再参与任何与数据搬运相关的事情，这样 CPU 就可以去处理别的事务</strong>。</p>
<p>那使用 DMA 控制器进行数据传输的过程究竟是什么样的呢？下面我们来具体看看。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234429.webp" alt="img"></p>
<p>具体过程：</p>
<ul>
<li>用户进程调用 read 方法，向操作系统发出 I/O 请求，请求读取数据到自己的内存缓冲区中，进程进入阻塞状态；</li>
<li>操作系统收到请求后，进一步将 I/O 请求发送 DMA，然后让 CPU 执行其他任务；</li>
<li>DMA 进一步将 I/O 请求发送给磁盘；</li>
<li>磁盘收到 DMA 的 I/O 请求，把数据从磁盘读取到磁盘控制器的缓冲区中，当磁盘控制器的缓冲区被读满后，向 DMA 发起中断信号，告知自己缓冲区已满；</li>
<li><strong>DMA 收到磁盘的信号，将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中，此时不占用 CPU，CPU 可以执行其他任务</strong>；</li>
<li>当 DMA 读取了足够多的数据，就会发送中断信号给 CPU；</li>
<li>CPU 收到 DMA 的信号，知道数据已经准备好，于是将数据从内核拷贝到用户空间，系统调用返回；</li>
</ul>
<p>可以看到， 整个数据传输的过程，CPU 不再参与数据搬运的工作，而是全程由 DMA 完成，但是 CPU 在这个过程中也是必不可少的，因为传输什么数据，从哪里传输到哪里，都需要 CPU 来告诉 DMA 控制器。</p>
<p>早期 DMA 只存在在主板上，如今由于 I/O 设备越来越多，数据传输的需求也不尽相同，所以每个 I/O 设备里面都有自己的 DMA 控制器。</p>
<hr>
<h3 id="传统的文件传输有多糟糕？"><a href="#传统的文件传输有多糟糕？" class="headerlink" title="传统的文件传输有多糟糕？"></a>传统的文件传输有多糟糕？</h3><p>如果服务端要提供文件传输的功能，我们能想到的最简单的方式是：将磁盘上的文件读取出来，然后通过网络协议发送给客户端。</p>
<p>传统 I/O 的工作方式是，数据读取和写入是从用户空间到内核空间来回复制，而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。</p>
<p>代码通常如下，一般会需要两个系统调用：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">read(file, tmp_buf, len);</span><br><span class="line">write(socket, tmp_buf, len);</span><br></pre></td></tr></table></figure>

<p>代码很简单，虽然就两行代码，但是这里面发生了不少的事情。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234533.webp" alt="img"></p>
<p>首先，期间共<strong>发生了 4 次用户态与内核态的上下文切换</strong>，因为发生了两次系统调用，一次是 <code>read()</code> ，一次是 <code>write()</code>，每次系统调用都得先从用户态切换到内核态，等内核完成任务后，再从内核态切换回用户态。</p>
<p>上下文切换到成本并不小，一次切换需要耗时几十纳秒到几微秒，虽然时间看上去很短，但是在高并发的场景下，这类时间容易被累积和放大，从而影响系统的性能。</p>
<p>其次，还<strong>发生了 4 次数据拷贝</strong>，其中两次是 DMA 的拷贝，另外两次则是通过 CPU 拷贝的，下面说一下这个过程：</p>
<ul>
<li><em>第一次拷贝</em>，把磁盘上的数据拷贝到操作系统内核的缓冲区里，这个拷贝的过程是通过 DMA 搬运的。</li>
<li><em>第二次拷贝</em>，把内核缓冲区的数据拷贝到用户的缓冲区里，于是我们应用程序就可以使用这部分数据了，这个拷贝到过程是由 CPU 完成的。</li>
<li><em>第三次拷贝</em>，把刚才拷贝到用户的缓冲区里的数据，再拷贝到内核的 socket 的缓冲区里，这个过程依然还是由 CPU 搬运的。</li>
<li><em>第四次拷贝</em>，把内核的 socket 缓冲区里的数据，拷贝到网卡的缓冲区里，这个过程又是由 DMA 搬运的。</li>
</ul>
<p>我们回过头看这个文件传输的过程，我们只是搬运一份数据，结果却搬运了 4 次，过多的数据拷贝无疑会消耗 CPU 资源，大大降低了系统性能。</p>
<p>这种简单又传统的文件传输方式，存在冗余的上文切换和数据拷贝，在高并发系统里是非常糟糕的，多了很多不必要的开销，会严重影响系统性能。</p>
<p>所以，<strong>要想提高文件传输的性能，就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数</strong>。</p>
<hr>
<h3 id="如何优化文件传输的性能？"><a href="#如何优化文件传输的性能？" class="headerlink" title="如何优化文件传输的性能？"></a>如何优化文件传输的性能？</h3><blockquote>
<p>先来看看，如何减少「用户态与内核态的上下文切换」的次数呢？</p>
</blockquote>
<p>读取磁盘数据的时候，之所以要发生上下文切换，这是因为用户空间没有权限操作磁盘或网卡，内核的权限最高，这些操作设备的过程都需要交由操作系统内核来完成，所以一般要通过内核去完成某些任务的时候，就需要使用操作系统提供的系统调用函数。</p>
<p>而一次系统调用必然会发生 2 次上下文切换：首先从用户态切换到内核态，当内核执行完任务后，再切换回用户态交由进程代码执行。</p>
<p>所以，<strong>要想减少上下文切换到次数，就要减少系统调用的次数</strong>。</p>
<blockquote>
<p>再来看看，如何减少「数据拷贝」的次数？</p>
</blockquote>
<p>在前面我们知道了，传统的文件传输方式会历经 4 次数据拷贝，而且这里面，「从内核的读缓冲区拷贝到用户的缓冲区里，再从用户的缓冲区里拷贝到 socket 的缓冲区里」，这个过程是没有必要的。</p>
<p>因为文件传输的应用场景中，在用户空间我们并不会对数据「再加工」，所以数据实际上可以不用搬运到用户空间，因此<strong>用户的缓冲区是没有必要存在的</strong>。</p>
<hr>
<h3 id="如何实现零拷贝？"><a href="#如何实现零拷贝？" class="headerlink" title="如何实现零拷贝？"></a>如何实现零拷贝？</h3><p>零拷贝技术实现的方式通常有 2 种：</p>
<ul>
<li>mmap + write</li>
<li>sendfile</li>
</ul>
<p>下面就谈一谈，它们是如何减少「上下文切换」和「数据拷贝」的次数。</p>
<h4 id="mmap-write"><a href="#mmap-write" class="headerlink" title="mmap + write"></a>mmap + write</h4><p>在前面我们知道，<code>read()</code> 系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区里，于是为了减少这一步开销，我们可以用 <code>mmap()</code> 替换 <code>read()</code> 系统调用函数。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">buf &#x3D; mmap(file, len);</span><br><span class="line">write(sockfd, buf, len);</span><br></pre></td></tr></table></figure>

<p><code>mmap()</code> 系统调用函数会直接把内核缓冲区里的数据「<strong>映射</strong>」到用户空间，这样，操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234194.webp" alt="img"></p>
<p>具体过程如下：</p>
<ul>
<li>应用进程调用了 <code>mmap()</code> 后，DMA 会把磁盘的数据拷贝到内核的缓冲区里。接着，应用进程跟操作系统内核「共享」这个缓冲区；</li>
<li>应用进程再调用 <code>write()</code>，操作系统直接将内核缓冲区的数据拷贝到 socket 缓冲区中，这一切都发生在内核态，由 CPU 来搬运数据；</li>
<li>最后，把内核的 socket 缓冲区里的数据，拷贝到网卡的缓冲区里，这个过程是由 DMA 搬运的。</li>
</ul>
<p>我们可以得知，通过使用 <code>mmap()</code> 来代替 <code>read()</code>， 可以减少一次数据拷贝的过程。</p>
<p>但这还不是最理想的零拷贝，因为仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里，而且仍然需要 4 次上下文切换，因为系统调用还是 2 次。</p>
<h4 id="sendfile"><a href="#sendfile" class="headerlink" title="sendfile"></a>sendfile</h4><p>在 Linux 内核版本 2.1 中，提供了一个专门发送文件的系统调用函数 <code>sendfile()</code>，函数形式如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#include &lt;sys&#x2F;socket.h&gt;</span><br><span class="line">ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);</span><br></pre></td></tr></table></figure>

<p>它的前两个参数分别是目的端和源端的文件描述符，后面两个参数是源端的偏移量和复制数据的长度，返回值是实际复制数据的长度。</p>
<p>首先，它可以替代前面的 <code>read()</code> 和 <code>write()</code> 这两个系统调用，这样就可以减少一次系统调用，也就减少了 2 次上下文切换的开销。</p>
<p>其次，该系统调用，可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里，不再拷贝到用户态，这样就只有 2 次上下文切换，和 3 次数据拷贝。如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234119.webp" alt="img"></p>
<p>但是这还不是真正的零拷贝技术，如果网卡支持 SG-DMA（<em>The Scatter-Gather Direct Memory Access</em>）技术（和普通的 DMA 有所不同），我们可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程。</p>
<p>你可以在你的 Linux 系统通过下面这个命令，查看网卡是否支持 scatter-gather 特性：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ ethtool -k eth0 | grep scatter-gather</span><br><span class="line">scatter-gather: on</span><br></pre></td></tr></table></figure>

<p>于是，从 Linux 内核 <code>2.4</code> 版本开始起，对于支持网卡支持 SG-DMA 技术的情况下， <code>sendfile()</code> 系统调用的过程发生了点变化，具体过程如下：</p>
<ul>
<li>第一步，通过 DMA 将磁盘上的数据拷贝到内核缓冲区里；</li>
<li>第二步，缓冲区描述符和数据长度传到 socket 缓冲区，这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里，此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中，这样就减少了一次数据拷贝；</li>
</ul>
<p>所以，这个过程之中，只进行了 2 次数据拷贝，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234426.webp" alt="img"></p>
<p>这就是所谓的<strong>零拷贝（*Zero-copy*）技术，因为我们没有在内存层面去拷贝数据，也就是说全程没有通过 CPU 来搬运数据，所有的数据都是通过 DMA 来进行传输的。</strong></p>
<p>零拷贝技术的文件传输方式相比传统文件传输的方式，减少了 2 次上下文切换和数据拷贝次数，<strong>只需要 2 次上下文切换和数据拷贝次数，就可以完成文件的传输，而且 2 次的数据拷贝过程，都不需要通过 CPU，2 次都是由 DMA 来搬运。</strong></p>
<p>所以，总体来看，<strong>零拷贝技术可以把文件传输的性能提高至少一倍以上</strong>。</p>
<h4 id="使用零拷贝技术的项目"><a href="#使用零拷贝技术的项目" class="headerlink" title="使用零拷贝技术的项目"></a>使用零拷贝技术的项目</h4><p>事实上，Kafka 这个开源项目，就利用了「零拷贝」技术，从而大幅提升了 I/O 的吞吐率，这也是 Kafka 在处理海量数据为什么这么快的原因之一。</p>
<p>如果你追溯 Kafka 文件传输的代码，你会发现，最终它调用了 Java NIO 库里的 <code>transferTo</code>方法：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Overridepublic </span><br><span class="line">long transferFrom(FileChannel fileChannel, long position, long count) throws IOException &#123; </span><br><span class="line">    return fileChannel.transferTo(position, count, socketChannel);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果 Linux 系统支持 <code>sendfile()</code> 系统调用，那么 <code>transferTo()</code> 实际上最后就会使用到 <code>sendfile()</code> 系统调用函数。</p>
<p>曾经有大佬专门写过程序测试过，在同样的硬件条件下，传统文件传输和零拷拷贝文件传输的性能差异，你可以看到下面这张测试数据图，使用了零拷贝能够缩短 <code>65%</code> 的时间，大幅度提升了机器传输数据的吞吐量。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234324.webp" alt="数据来源于：https://developer.ibm.com/articles/j-zerocopy/">数据来源于：<a target="_blank" rel="noopener" href="https://developer.ibm.com/articles/j-zerocopy/">https://developer.ibm.com/articles/j-zerocopy/</a></p>
<p>另外，Nginx 也支持零拷贝技术，一般默认是开启零拷贝技术，这样有利于提高文件传输的效率，是否开启零拷贝技术的配置如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">http &#123;</span><br><span class="line">...</span><br><span class="line">    sendfile on</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>sendfile 配置的具体意思: </p>
<ul>
<li>设置为 on 表示，使用零拷贝技术来传输文件：sendfile ，这样只需要 2 次上下文切换，和 2 次数据拷贝。</li>
<li>设置为 off 表示，使用传统的文件传输技术：read + write，这时就需要 4 次上下文切换，和 4 次数据拷贝。</li>
</ul>
<p>当然，要使用 sendfile，Linux 内核版本必须要 2.1 以上的版本。</p>
<hr>
<h3 id="PageCache-有什么作用？"><a href="#PageCache-有什么作用？" class="headerlink" title="PageCache 有什么作用？"></a>PageCache 有什么作用？</h3><p>回顾前面说道文件传输过程，其中第一步都是先需要先把磁盘文件数据拷贝「内核缓冲区」里，这个「内核缓冲区」实际上是<strong>磁盘高速缓存（*PageCache*）</strong>。</p>
<p>由于零拷贝使用了 PageCache 技术，可以使得零拷贝进一步提升了性能，我们接下来看看 PageCache 是如何做到这一点的。</p>
<p>读写磁盘相比读写内存的速度慢太多了，所以我们应该想办法把「读写磁盘」替换成「读写内存」。于是，我们会通过 DMA 把磁盘里的数据搬运到内存里，这样就可以用读内存替换读磁盘。</p>
<p>但是，内存空间远比磁盘要小，内存注定只能拷贝磁盘里的一小部分数据。</p>
<p>那问题来了，选择哪些磁盘数据拷贝到内存呢？</p>
<p>我们都知道程序运行的时候，具有「局部性」，所以通常，刚被访问的数据在短时间内再次被访问的概率很高，于是我们可以用 <strong>PageCache 来缓存最近被访问的数据</strong>，当空间不足时淘汰最久未被访问的缓存。</p>
<p>所以，读磁盘数据的时候，优先在 PageCache 找，如果数据存在则可以直接返回；如果没有，则从磁盘中读取，然后缓存 PageCache 中。</p>
<p>还有一点，读取磁盘数据的时候，需要找到数据所在的位置，但是对于机械磁盘来说，就是通过磁头旋转到数据所在的扇区，再开始「顺序」读取数据，但是旋转磁头这个物理动作是非常耗时的，为了降低它的影响，<strong>PageCache 使用了「预读功能」</strong>。</p>
<p>比如，假设 read 方法每次只会读 <code>32 KB</code> 的字节，虽然 read 刚开始只会读 0 ～ 32 KB 的字节，但内核会把其后面的 32～64 KB 也读取到 PageCache，这样后面读取 32～64 KB 的成本就很低，如果在 32～64 KB 淘汰出 PageCache 前，进程读取到它了，收益就非常大。</p>
<p>所以，PageCache 的优点主要是两个：</p>
<ul>
<li>缓存最近被访问的数据；</li>
<li>预读功能；</li>
</ul>
<p>这两个做法，将大大提高读写磁盘的性能。</p>
<p><strong>但是，在传输大文件（GB 级别的文件）的时候，PageCache 会不起作用，那就白白浪费 DMA 多做的一次数据拷贝，造成性能的降低，即使使用了 PageCache 的零拷贝也会损失性能</strong></p>
<p>这是因为如果你有很多 GB 级别文件需要传输，每当用户访问这些大文件的时候，内核就会把它们载入 PageCache 中，于是 PageCache 空间很快被这些大文件占满。</p>
<p>另外，由于文件太大，可能某些部分的文件数据被再次访问的概率比较低，这样就会带来 2 个问题：</p>
<ul>
<li>PageCache 由于长时间被大文件占据，其他「热点」的小文件可能就无法充分使用到 PageCache，于是这样磁盘读写的性能就会下降了；</li>
<li>PageCache 中的大文件数据，由于没有享受到缓存带来的好处，但却耗费 DMA 多拷贝到 PageCache 一次；</li>
</ul>
<p>所以，针对大文件的传输，不应该使用 PageCache，也就是说不应该使用零拷贝技术，因为可能由于 PageCache 被大文件占据，而导致「热点」小文件无法利用到 PageCache，这样在高并发的环境下，会带来严重的性能问题。</p>
<hr>
<h3 id="大文件传输用什么方式实现？"><a href="#大文件传输用什么方式实现？" class="headerlink" title="大文件传输用什么方式实现？"></a>大文件传输用什么方式实现？</h3><p>那针对大文件的传输，我们应该使用什么方式呢？</p>
<p>我们先来看看最初的例子，当调用 read 方法读取文件时，进程实际上会阻塞在 read 方法调用，因为要等待磁盘数据的返回，如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001235115.webp" alt="img"></p>
<p>具体过程：</p>
<ul>
<li>当调用 read 方法时，会阻塞着，此时内核会向磁盘发起 I/O 请求，磁盘收到请求后，便会寻址，当磁盘数据准备好后，就会向内核发起 I/O 中断，告知内核磁盘数据已经准备好；</li>
<li>内核收到 I/O 中断后，就将数据从磁盘控制器缓冲区拷贝到 PageCache 里；</li>
<li>最后，内核再把 PageCache 中的数据拷贝到用户缓冲区，于是 read 调用就正常返回了。</li>
</ul>
<p>对于阻塞的问题，可以用异步 I/O 来解决，它工作方式如下图：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601001234016.webp" alt="img"></p>
<p>它把读操作分为两部分：</p>
<ul>
<li>前半部分，内核向磁盘发起读请求，但是可以<strong>不等待数据就位就可以返回</strong>，于是进程此时可以处理其他任务；</li>
<li>后半部分，当内核将磁盘中的数据拷贝到进程缓冲区后，进程将接收到内核的<strong>通知</strong>，再去处理数据；</li>
</ul>
<p>而且，我们可以发现，异步 I/O 并没有涉及到 PageCache，所以使用异步 I/O 就意味着要绕开 PageCache。</p>
<p>绕开 PageCache 的 I/O 叫直接 I/O，使用 PageCache 的 I/O 则叫缓存 I/O。通常，对于磁盘，异步 I/O 只支持直接 I/O。</p>
<p>前面也提到，大文件的传输不应该使用 PageCache，因为可能由于 PageCache 被大文件占据，而导致「热点」小文件无法利用到 PageCache。</p>
<p>于是，<strong>在高并发的场景下，针对大文件的传输的方式，应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术</strong>。</p>
<p>直接 I/O 应用场景常见的两种：</p>
<ul>
<li>应用程序已经实现了磁盘数据的缓存，那么可以不需要 PageCache 再次缓存，减少额外的性能损耗。在 MySQL 数据库中，可以通过参数设置开启直接 I/O，默认是不开启；</li>
<li>传输大文件的时候，由于大文件难以命中 PageCache 缓存，而且会占满 PageCache 导致「热点」文件无法充分利用缓存，从而增大了性能开销，因此，这时应该使用直接 I/O。</li>
</ul>
<p>另外，由于直接 I/O 绕过了 PageCache，就无法享受内核的这两点的优化：</p>
<ul>
<li>内核的 I/O 调度算法会缓存尽可能多的 I/O 请求在 PageCache 中，最后「<strong>合并</strong>」成一个更大的 I/O 请求再发给磁盘，这样做是为了减少磁盘的寻址操作；</li>
<li>内核也会「<strong>预读</strong>」后续的 I/O 请求放在 PageCache 中，一样是为了减少对磁盘的操作；</li>
</ul>
<p>于是，传输大文件的时候，使用「异步 I/O + 直接 I/O」了，就可以无阻塞地读取文件了。</p>
<p>所以，传输文件的时候，我们要根据文件的大小来使用不同的方式：</p>
<ul>
<li>传输大文件的时候，使用「异步 I/O + 直接 I/O」；</li>
<li>传输小文件的时候，则使用「零拷贝技术」；</li>
</ul>
<p>在 Nginx 中，我们可以用如下配置，来根据文件的大小来使用不同的方式：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">location &#x2F;video&#x2F; &#123; </span><br><span class="line">    sendfile on; </span><br><span class="line">    aio on; </span><br><span class="line">    directio 1024m; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当文件大小大于 <code>directio</code> 值后，使用「异步 I/O + 直接 I/O」，否则使用「零拷贝技术」。</p>
<hr>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>早期 I/O 操作，内存与磁盘的数据传输的工作都是由 CPU 完成的，而此时 CPU 不能执行其他任务，会特别浪费 CPU 资源。</p>
<p>于是，为了解决这一问题，DMA 技术就出现了，每个 I/O 设备都有自己的 DMA 控制器，通过这个 DMA 控制器，CPU 只需要告诉 DMA 控制器，我们要传输什么数据，从哪里来，到哪里去，就可以放心离开了。后续的实际数据传输工作，都会由 DMA 控制器来完成，CPU 不需要参与数据传输的工作。</p>
<p>传统 IO 的工作方式，从硬盘读取数据，然后再通过网卡向外发送，我们需要进行 4 上下文切换，和 4 次数据拷贝，其中 2 次数据拷贝发生在内存里的缓冲区和对应的硬件设备之间，这个是由 DMA 完成，另外 2 次则发生在内核态和用户态之间，这个数据搬移工作是由 CPU 完成的。</p>
<p>为了提高文件传输的性能，于是就出现了零拷贝技术，它通过一次系统调用（<code>sendfile</code> 方法）合并了磁盘读取与网络发送两个操作，降低了上下文切换次数。另外，拷贝数据都是发生在内核中的，天然就降低了数据拷贝的次数。</p>
<p>Kafka 和 Nginx 都有实现零拷贝技术，这将大大提高文件传输的性能。</p>
<p>零拷贝技术是基于 PageCache 的，PageCache 会缓存最近访问的数据，提升了访问缓存数据的性能，同时，为了解决机械硬盘寻址慢的问题，它还协助 I/O 调度算法实现了 IO 合并与预读，这也是顺序读比随机读性能好的原因。这些优势，进一步提升了零拷贝的性能。</p>
<p>需要注意的是，零拷贝技术是不允许进程对文件内容作进一步的加工的，比如压缩数据再发送。</p>
<p>另外，当传输大文件时，不能使用零拷贝，因为可能由于 PageCache 被大文件占据，而导致「热点」小文件无法利用到 PageCache，并且大文件的缓存命中率不高，这时就需要使用「异步 IO + 直接 IO 」的方式。</p>
<p>在 Nginx 里，可以通过配置，设定一个文件大小阈值，针对大文件使用异步 IO 和直接 IO，而对小文件使用零拷贝。</p>
<p>（END）</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/other/" rel="tag">other</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-docker/最新整理之--docker-compose参数及命令"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/docker/%E6%9C%80%E6%96%B0%E6%95%B4%E7%90%86%E4%B9%8B--docker-compose%E5%8F%82%E6%95%B0%E5%8F%8A%E5%91%BD%E4%BB%A4/"
    >最新整理之--docker-compose参数及命令.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/docker/%E6%9C%80%E6%96%B0%E6%95%B4%E7%90%86%E4%B9%8B--docker-compose%E5%8F%82%E6%95%B0%E5%8F%8A%E5%91%BD%E4%BB%A4/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/docker/">docker</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="最新整理之–docker-compose参数及命令"><a href="#最新整理之–docker-compose参数及命令" class="headerlink" title="最新整理之–docker-compose参数及命令"></a>最新整理之–docker-compose参数及命令</h1><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br><span class="line">546</span><br><span class="line">547</span><br><span class="line">548</span><br><span class="line">549</span><br><span class="line">550</span><br><span class="line">551</span><br><span class="line">552</span><br><span class="line">553</span><br><span class="line">554</span><br><span class="line">555</span><br><span class="line">556</span><br><span class="line">557</span><br><span class="line">558</span><br><span class="line">559</span><br><span class="line">560</span><br><span class="line">561</span><br><span class="line">562</span><br><span class="line">563</span><br><span class="line">564</span><br><span class="line">565</span><br><span class="line">566</span><br><span class="line">567</span><br><span class="line">568</span><br><span class="line">569</span><br><span class="line">570</span><br><span class="line">571</span><br><span class="line">572</span><br><span class="line">573</span><br><span class="line">574</span><br><span class="line">575</span><br><span class="line">576</span><br><span class="line">577</span><br><span class="line">578</span><br><span class="line">579</span><br><span class="line">580</span><br><span class="line">581</span><br><span class="line">582</span><br><span class="line">583</span><br><span class="line">584</span><br><span class="line">585</span><br><span class="line">586</span><br><span class="line">587</span><br><span class="line">588</span><br><span class="line">589</span><br><span class="line">590</span><br><span class="line">591</span><br><span class="line">592</span><br><span class="line">593</span><br><span class="line">594</span><br><span class="line">595</span><br><span class="line">596</span><br><span class="line">597</span><br><span class="line">598</span><br><span class="line">599</span><br><span class="line">600</span><br><span class="line">601</span><br><span class="line">602</span><br><span class="line">603</span><br><span class="line">604</span><br><span class="line">605</span><br><span class="line">606</span><br><span class="line">607</span><br><span class="line">608</span><br><span class="line">609</span><br><span class="line">610</span><br><span class="line">611</span><br><span class="line">612</span><br><span class="line">613</span><br><span class="line">614</span><br><span class="line">615</span><br><span class="line">616</span><br><span class="line">617</span><br><span class="line">618</span><br><span class="line">619</span><br><span class="line">620</span><br><span class="line">621</span><br><span class="line">622</span><br><span class="line">623</span><br><span class="line">624</span><br><span class="line">625</span><br><span class="line">626</span><br><span class="line">627</span><br><span class="line">628</span><br><span class="line">629</span><br><span class="line">630</span><br><span class="line">631</span><br><span class="line">632</span><br><span class="line">633</span><br><span class="line">634</span><br><span class="line">635</span><br><span class="line">636</span><br><span class="line">637</span><br><span class="line">638</span><br><span class="line">639</span><br><span class="line">640</span><br><span class="line">641</span><br><span class="line">642</span><br><span class="line">643</span><br><span class="line">644</span><br><span class="line">645</span><br><span class="line">646</span><br><span class="line">647</span><br><span class="line">648</span><br><span class="line">649</span><br><span class="line">650</span><br><span class="line">651</span><br><span class="line">652</span><br><span class="line">653</span><br><span class="line">654</span><br><span class="line">655</span><br><span class="line">656</span><br><span class="line">657</span><br><span class="line">658</span><br><span class="line">659</span><br><span class="line">660</span><br><span class="line">661</span><br><span class="line">662</span><br><span class="line">663</span><br><span class="line">664</span><br><span class="line">665</span><br><span class="line">666</span><br><span class="line">667</span><br><span class="line">668</span><br><span class="line">669</span><br><span class="line">670</span><br><span class="line">671</span><br><span class="line">672</span><br><span class="line">673</span><br><span class="line">674</span><br><span class="line">675</span><br><span class="line">676</span><br><span class="line">677</span><br><span class="line">678</span><br><span class="line">679</span><br><span class="line">680</span><br><span class="line">681</span><br><span class="line">682</span><br><span class="line">683</span><br><span class="line">684</span><br><span class="line">685</span><br><span class="line">686</span><br><span class="line">687</span><br><span class="line">688</span><br><span class="line">689</span><br><span class="line">690</span><br><span class="line">691</span><br><span class="line">692</span><br><span class="line">693</span><br><span class="line">694</span><br><span class="line">695</span><br><span class="line">696</span><br><span class="line">697</span><br><span class="line">698</span><br><span class="line">699</span><br><span class="line">700</span><br><span class="line">701</span><br><span class="line">702</span><br><span class="line">703</span><br><span class="line">704</span><br><span class="line">705</span><br><span class="line">706</span><br><span class="line">707</span><br><span class="line">708</span><br><span class="line">709</span><br><span class="line">710</span><br><span class="line">711</span><br><span class="line">712</span><br><span class="line">713</span><br><span class="line">714</span><br><span class="line">715</span><br><span class="line">716</span><br><span class="line">717</span><br><span class="line">718</span><br><span class="line">719</span><br><span class="line">720</span><br><span class="line">721</span><br><span class="line">722</span><br><span class="line">723</span><br><span class="line">724</span><br><span class="line">725</span><br><span class="line">726</span><br><span class="line">727</span><br><span class="line">728</span><br><span class="line">729</span><br><span class="line">730</span><br><span class="line">731</span><br><span class="line">732</span><br><span class="line">733</span><br><span class="line">734</span><br><span class="line">735</span><br><span class="line">736</span><br><span class="line">737</span><br><span class="line">738</span><br><span class="line">739</span><br><span class="line">740</span><br><span class="line">741</span><br><span class="line">742</span><br><span class="line">743</span><br><span class="line">744</span><br><span class="line">745</span><br><span class="line">746</span><br><span class="line">747</span><br><span class="line">748</span><br><span class="line">749</span><br><span class="line">750</span><br><span class="line">751</span><br><span class="line">752</span><br><span class="line">753</span><br><span class="line">754</span><br><span class="line">755</span><br><span class="line">756</span><br><span class="line">757</span><br><span class="line">758</span><br><span class="line">759</span><br><span class="line">760</span><br><span class="line">761</span><br><span class="line">762</span><br><span class="line">763</span><br><span class="line">764</span><br><span class="line">765</span><br><span class="line">766</span><br><span class="line">767</span><br><span class="line">768</span><br><span class="line">769</span><br><span class="line">770</span><br><span class="line">771</span><br><span class="line">772</span><br><span class="line">773</span><br><span class="line">774</span><br><span class="line">775</span><br><span class="line">776</span><br><span class="line">777</span><br><span class="line">778</span><br><span class="line">779</span><br><span class="line">780</span><br><span class="line">781</span><br><span class="line">782</span><br><span class="line">783</span><br><span class="line">784</span><br><span class="line">785</span><br><span class="line">786</span><br><span class="line">787</span><br><span class="line">788</span><br><span class="line">789</span><br><span class="line">790</span><br><span class="line">791</span><br><span class="line">792</span><br><span class="line">793</span><br><span class="line">794</span><br><span class="line">795</span><br><span class="line">796</span><br><span class="line">797</span><br><span class="line">798</span><br><span class="line">799</span><br><span class="line">800</span><br><span class="line">801</span><br><span class="line">802</span><br><span class="line">803</span><br><span class="line">804</span><br><span class="line">805</span><br><span class="line">806</span><br><span class="line">807</span><br><span class="line">808</span><br><span class="line">809</span><br><span class="line">810</span><br><span class="line">811</span><br><span class="line">812</span><br><span class="line">813</span><br><span class="line">814</span><br><span class="line">815</span><br><span class="line">816</span><br><span class="line">817</span><br><span class="line">818</span><br><span class="line">819</span><br><span class="line">820</span><br><span class="line">821</span><br><span class="line">822</span><br><span class="line">823</span><br><span class="line">824</span><br><span class="line">825</span><br><span class="line">826</span><br><span class="line">827</span><br><span class="line">828</span><br><span class="line">829</span><br><span class="line">830</span><br><span class="line">831</span><br><span class="line">832</span><br><span class="line">833</span><br><span class="line">834</span><br><span class="line">835</span><br><span class="line">836</span><br><span class="line">837</span><br><span class="line">838</span><br><span class="line">839</span><br><span class="line">840</span><br><span class="line">841</span><br><span class="line">842</span><br><span class="line">843</span><br><span class="line">844</span><br><span class="line">845</span><br><span class="line">846</span><br><span class="line">847</span><br><span class="line">848</span><br><span class="line">849</span><br><span class="line">850</span><br><span class="line">851</span><br><span class="line">852</span><br><span class="line">853</span><br><span class="line">854</span><br><span class="line">855</span><br><span class="line">856</span><br><span class="line">857</span><br><span class="line">858</span><br><span class="line">859</span><br><span class="line">860</span><br><span class="line">861</span><br><span class="line">862</span><br><span class="line">863</span><br><span class="line">864</span><br><span class="line">865</span><br><span class="line">866</span><br><span class="line">867</span><br><span class="line">868</span><br><span class="line">869</span><br><span class="line">870</span><br><span class="line">871</span><br><span class="line">872</span><br><span class="line">873</span><br><span class="line">874</span><br><span class="line">875</span><br><span class="line">876</span><br><span class="line">877</span><br><span class="line">878</span><br><span class="line">879</span><br><span class="line">880</span><br><span class="line">881</span><br><span class="line">882</span><br><span class="line">883</span><br><span class="line">884</span><br><span class="line">885</span><br><span class="line">886</span><br><span class="line">887</span><br><span class="line">888</span><br><span class="line">889</span><br><span class="line">890</span><br><span class="line">891</span><br><span class="line">892</span><br><span class="line">893</span><br><span class="line">894</span><br><span class="line">895</span><br><span class="line">896</span><br><span class="line">897</span><br><span class="line">898</span><br><span class="line">899</span><br><span class="line">900</span><br><span class="line">901</span><br><span class="line">902</span><br><span class="line">903</span><br><span class="line">904</span><br><span class="line">905</span><br><span class="line">906</span><br><span class="line">907</span><br><span class="line">908</span><br><span class="line">909</span><br><span class="line">910</span><br><span class="line">911</span><br><span class="line">912</span><br><span class="line">913</span><br><span class="line">914</span><br><span class="line">915</span><br><span class="line">916</span><br><span class="line">917</span><br><span class="line">918</span><br><span class="line">919</span><br><span class="line">920</span><br><span class="line">921</span><br><span class="line">922</span><br><span class="line">923</span><br><span class="line">924</span><br><span class="line">925</span><br><span class="line">926</span><br><span class="line">927</span><br><span class="line">928</span><br><span class="line">929</span><br><span class="line">930</span><br><span class="line">931</span><br><span class="line">932</span><br><span class="line">933</span><br><span class="line">934</span><br><span class="line">935</span><br><span class="line">936</span><br><span class="line">937</span><br><span class="line">938</span><br><span class="line">939</span><br><span class="line">940</span><br><span class="line">941</span><br><span class="line">942</span><br><span class="line">943</span><br><span class="line">944</span><br><span class="line">945</span><br><span class="line">946</span><br><span class="line">947</span><br><span class="line">948</span><br><span class="line">949</span><br><span class="line">950</span><br><span class="line">951</span><br><span class="line">952</span><br><span class="line">953</span><br><span class="line">954</span><br><span class="line">955</span><br><span class="line">956</span><br><span class="line">957</span><br><span class="line">958</span><br><span class="line">959</span><br><span class="line">960</span><br><span class="line">961</span><br><span class="line">962</span><br><span class="line">963</span><br><span class="line">964</span><br><span class="line">965</span><br><span class="line">966</span><br><span class="line">967</span><br><span class="line">968</span><br><span class="line">969</span><br><span class="line">970</span><br><span class="line">971</span><br><span class="line">972</span><br><span class="line">973</span><br><span class="line">974</span><br><span class="line">975</span><br><span class="line">976</span><br><span class="line">977</span><br><span class="line">978</span><br><span class="line">979</span><br><span class="line">980</span><br><span class="line">981</span><br><span class="line">982</span><br><span class="line">983</span><br><span class="line">984</span><br><span class="line">985</span><br><span class="line">986</span><br><span class="line">987</span><br><span class="line">988</span><br><span class="line">989</span><br><span class="line">990</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">一、前言</span></span><br><span class="line"></span><br><span class="line"><span class="string">如果觉得文章有用，请帮忙波哥分享转发！~</span></span><br><span class="line"><span class="string">Compose是一个用于定义和运行多容器Docker应用程序的工具。使用Compose，您可以使用YAML文件来配置应用程序的服务。然后，使用单个命令，您可以从配置中创建并启动所有服务。</span></span><br><span class="line"><span class="string">Compose可以.yml</span> <span class="string">或</span> <span class="string">.yaml</span> <span class="string">作为文件扩展名。</span></span><br><span class="line"><span class="string">Compose适用于所有环境：生产，登台，开发，测试以及CI工作流程。</span></span><br><span class="line"><span class="string">关于</span> <span class="string">docker</span> <span class="string">compose</span> <span class="string">技术可以查看官方文档</span> <span class="string">Docker</span> <span class="string">Compose</span></span><br><span class="line"> </span><br><span class="line"><span class="string">以下的内容是确立在已经下载好</span> <span class="string">Docker</span> <span class="string">以及</span> <span class="string">Docker</span> <span class="string">Compose，可参看</span> <span class="string">Docker</span> <span class="string">Compose</span> <span class="string">的官方安装教程</span> <span class="string">Install</span> <span class="string">Docker</span> <span class="string">Compose</span></span><br><span class="line"><span class="string">二、Docker</span> <span class="string">Compose</span> <span class="string">配置文件的构建参数说明</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.1</span> <span class="string">build</span></span><br><span class="line"><span class="string">服务除了可以基于指定的镜像，还可以基于一份</span> <span class="string">Dockerfile，在使用</span> <span class="string">up</span> <span class="string">启动之时执行构建任务，这个构建标签就是</span> <span class="string">build，它可以指定</span> <span class="string">Dockerfile</span> <span class="string">所在文件夹的路径。Compose</span> <span class="string">将会利用它自动构建这个镜像，然后使用这个镜像启动服务容器。</span></span><br><span class="line"><span class="string">build</span> <span class="string">可以指定为包含构建上下文路径的字符串：</span></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">webapp:</span></span><br><span class="line">    <span class="string">build:./dir</span></span><br><span class="line"><span class="string">或者，作为具有在上下文中指定的路径的对象以及可选的Dockerfile和args：</span></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">webapp:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="string">context:./dir</span></span><br><span class="line">      <span class="string">dockerfile:Dockerfile-alternate</span></span><br><span class="line">      <span class="attr">args:</span></span><br><span class="line">        <span class="string">buildno:1</span></span><br><span class="line"><span class="string">如果您指定image以及build，则使用以下指定的webapp和可选项对构建的图像进行命名：</span></span><br><span class="line"><span class="string">这会在./dir目录下生成一个名为webapp和标记的图像tag。</span></span><br><span class="line"><span class="string">build:./dir</span></span><br><span class="line"><span class="string">image:webapp:tag</span></span><br><span class="line"><span class="string">注意:当用(Version</span> <span class="number">3</span><span class="string">)</span> <span class="string">Compose</span> <span class="string">文件在群集模式下部署堆栈时，该选项被忽略。因为</span> <span class="string">docker</span> <span class="string">stack</span> <span class="string">命令只接受预先构建的镜像。</span></span><br><span class="line"><span class="number">2.2</span> <span class="string">context</span></span><br><span class="line"><span class="string">context</span> <span class="string">选项可以是</span> <span class="string">Dockerfile</span> <span class="string">的文件路径，也可以是到链接到</span> <span class="string">git</span> <span class="string">仓库的</span> <span class="string">url。</span></span><br><span class="line"><span class="string">当提供的值是相对路径时，它被解析为相对于撰写文件的路径，此目录也是发送到</span> <span class="string">Docker</span> <span class="string">守护进程的</span> <span class="string">context。</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:./dir</span></span><br><span class="line"><span class="number">2.3</span> <span class="string">dockerfile</span></span><br><span class="line"><span class="string">使用此</span> <span class="string">dockerfile</span> <span class="string">文件来构建，必须指定构建路径。</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="string">dockerfile:Dockerfile-alternate</span></span><br><span class="line"><span class="number">2.4</span> <span class="string">args</span></span><br><span class="line"><span class="string">添加构建参数，这些参数是仅在构建过程中可访问的环境变量。</span></span><br><span class="line"><span class="string">首先，在Dockerfile中指定参数：</span></span><br><span class="line"><span class="string">ARG</span> <span class="string">buildno</span></span><br><span class="line"><span class="string">ARG</span> <span class="string">gitcommithash</span></span><br><span class="line"> </span><br><span class="line"><span class="string">RUN</span> <span class="string">echo</span> <span class="string">&quot;Build number: $buildno&quot;</span></span><br><span class="line"><span class="string">RUN</span> <span class="string">echo</span> <span class="string">&quot;Based on commit: $gitcommithash&quot;</span></span><br><span class="line"><span class="string">然后在build键下指定参数，可以传递映射或列表：</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="attr">args:</span></span><br><span class="line">    <span class="string">buildno:1</span></span><br><span class="line">    <span class="string">gitcommithash:cdc3b19</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="attr">args:</span></span><br><span class="line">    <span class="string">-buildno=1</span></span><br><span class="line">    <span class="string">-gitcommithash=cdc3b19</span></span><br><span class="line"></span><br><span class="line"><span class="string">注意：在Dockerfile中，如果ARG在FROM指令之前指定，</span> <span class="string">ARG则在构建说明中不可用FROM。如果您需要在两个位置都可以使用参数，请在FROM指令下指定它。</span></span><br><span class="line"><span class="string">您可以在指定构建参数时省略该值，在这种情况下，它在构建时的值是运行Compose的环境中的值。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">args:</span></span><br><span class="line">  <span class="string">-buildno</span></span><br><span class="line">  <span class="string">-gitcommithash</span></span><br><span class="line"><span class="string">注意：YAML布尔值（true，false，yes，no，on，off）必须用引号括起来，这样分析器会将它们解释为字符串。</span></span><br><span class="line"><span class="number">2.5</span> <span class="string">cache_from</span></span><br><span class="line"><span class="string">编写缓存解析镜像列表，此选项是v3.2中的新选项。</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="attr">cache_from:</span></span><br><span class="line">    <span class="string">-alpine:latest</span></span><br><span class="line">    <span class="string">-corp/web_app:3.14</span></span><br><span class="line"><span class="number">2.6</span> <span class="string">labels</span></span><br><span class="line"><span class="string">使用</span> <span class="string">Docker标签</span> <span class="string">将元数据添加到生成的镜像中，可以使用数组或字典。</span></span><br><span class="line"><span class="string">建议使用反向</span> <span class="string">DNS</span> <span class="string">标记来防止签名与其他软件所使用的签名冲突，此选项是v3.3中的新选项。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="string">com.example.description:&quot;Accountingwebapp&quot;</span></span><br><span class="line">    <span class="string">com.example.department:&quot;Finance&quot;</span></span><br><span class="line">    <span class="string">com.example.label-with-empty-value:&quot;&quot;</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="string">-&quot;com.example.description=Accountingwebapp&quot;</span></span><br><span class="line">    <span class="string">-&quot;com.example.department=Finance&quot;</span></span><br><span class="line">    <span class="string">-&quot;com.example.label-with-empty-value&quot;</span></span><br><span class="line"><span class="number">2.7</span> <span class="string">shm_size</span></span><br><span class="line"><span class="string">设置容器</span> <span class="string">/dev/shm</span> <span class="string">分区的大小，值为表示字节的整数值或表示字符的字符串</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="string">shm_size:&#x27;2gb&#x27;</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="string">shm_size:10000000</span></span><br><span class="line"><span class="number">2.8</span> <span class="string">target</span></span><br><span class="line"><span class="string">根据对应的</span> <span class="string">Dockerfile</span> <span class="string">构建指定</span> <span class="string">Stage</span></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line">  <span class="string">context:.</span></span><br><span class="line">  <span class="string">target:prod</span></span><br><span class="line"><span class="number">2.9</span> <span class="string">cap_add，cap_drop</span></span><br><span class="line"><span class="string">添加或删除容器功能，可查看</span> <span class="string">man</span> <span class="number">7</span> <span class="string">capabilities</span></span><br><span class="line"><span class="attr">cap_add:</span></span><br><span class="line">  <span class="string">-ALL</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">cap_drop:</span></span><br><span class="line">  <span class="string">-NET_ADMIN</span></span><br><span class="line">  <span class="string">-SYS_ADMIN</span></span><br><span class="line"></span><br><span class="line"><span class="string">注意:当用(Version</span> <span class="number">3</span><span class="string">)</span> <span class="string">Compose</span> <span class="string">文件在群集模式下部署堆栈时，该选项被忽略。因为</span> <span class="string">docker</span> <span class="string">stack</span> <span class="string">命令只接受预先构建的镜像</span></span><br><span class="line"><span class="number">2.10</span> <span class="string">cgroup_parent</span></span><br><span class="line"><span class="string">可以为容器选择一个可选的父</span> <span class="string">cgroup_parent</span></span><br><span class="line"><span class="string">cgroup_parent:m-executor-abcd</span></span><br><span class="line"><span class="string">注意：当</span> <span class="string">使用（Version</span> <span class="number">3</span><span class="string">）Compose</span> <span class="string">文件在群集模式下部署堆栈时，忽略此选项</span></span><br><span class="line"><span class="number">2.11</span><span class="string">.</span> <span class="string">command</span></span><br><span class="line"><span class="string">覆盖容器启动后默认执行的命令</span></span><br><span class="line"><span class="string">command:bundle</span> <span class="string">exec</span> <span class="string">thin</span> <span class="string">-p</span> <span class="number">3000</span></span><br><span class="line"><span class="string">该命令也可以是一个列表，方式类似于dockerfile：</span></span><br><span class="line"><span class="string">command:[&quot;bundle&quot;,&quot;exec&quot;,&quot;thin&quot;,&quot;-p&quot;,&quot;3000&quot;]</span></span><br><span class="line"><span class="number">2.12</span><span class="string">.</span> <span class="string">configs</span></span><br><span class="line"><span class="string">使用服务</span> <span class="string">configs</span> <span class="string">配置为每个服务赋予相应的访问权限，支持两种不同的语法</span></span><br><span class="line"><span class="attr">Note:</span> <span class="string">配置必须存在或在</span> <span class="string">configs</span> <span class="string">此堆栈文件的顶层中定义，否则堆栈部署失效</span></span><br><span class="line"><span class="number">2.12</span><span class="number">.1</span> <span class="string">SHORT</span> <span class="string">语法</span></span><br><span class="line"><span class="string">SHORT</span> <span class="string">语法只能指定配置名称，这允许容器访问配置并将其安装在</span> <span class="string">/&lt;config_name&gt;</span> <span class="string">容器内，源名称和目标装入点都设为配置名称。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis:latest</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">replicas:1</span></span><br><span class="line">    <span class="attr">configs:</span></span><br><span class="line">      <span class="string">-my_config</span></span><br><span class="line">      <span class="string">-my_other_config</span></span><br><span class="line"><span class="attr">configs:</span></span><br><span class="line">  <span class="attr">my_config:</span></span><br><span class="line">    <span class="string">file:./my_config.txt</span></span><br><span class="line">  <span class="attr">my_other_config:</span></span><br><span class="line">    <span class="string">external:true</span></span><br><span class="line"></span><br><span class="line"><span class="string">以上实例使用</span> <span class="string">SHORT</span> <span class="string">语法将</span> <span class="string">redis</span> <span class="string">服务访问授予</span> <span class="string">my_config</span> <span class="string">和</span> <span class="string">my_other_config</span> <span class="string">,并被</span> <span class="string">my_other_config</span> <span class="string">定义为外部资源，这意味着它已经在</span> <span class="string">Docker</span> <span class="string">中定义。可以通过</span> <span class="string">docker</span> <span class="string">config</span> <span class="string">create</span> <span class="string">命令或通过另一个堆栈部署。如果外部部署配置都不存在，则堆栈部署会失败并出现</span> <span class="string">config</span> <span class="string">not</span> <span class="string">found</span> <span class="string">错误。</span></span><br><span class="line"><span class="string">注意:</span> <span class="string">config</span> <span class="string">定义仅在</span> <span class="number">3.3</span> <span class="string">版本或在更高版本的撰写文件格式中受支持，YAML</span> <span class="string">的布尔值（true,</span> <span class="literal">false</span><span class="string">,</span> <span class="literal">yes</span><span class="string">,</span> <span class="literal">no</span><span class="string">,</span> <span class="string">on,</span> <span class="string">off）必须要使用引号引起来（单引号、双引号均可），否则会当成字符串解析。</span></span><br><span class="line"><span class="number">2.12</span><span class="number">.2</span> <span class="string">LONG</span> <span class="string">语法</span></span><br><span class="line"><span class="string">LONG</span> <span class="string">语法提供了创建服务配置的更加详细的信息。</span></span><br><span class="line"><span class="string">source：Docker</span> <span class="string">中存在的配置的名称</span></span><br><span class="line"><span class="string">target：要在服务的任务中装载的文件的路径或名称。如果未指定则默认为</span> <span class="string">/&lt;source&gt;</span> </span><br><span class="line"><span class="string">uid</span> <span class="string">和</span> <span class="string">gid：在服务的任务容器中拥有安装的配置文件的数字</span> <span class="string">UID</span> <span class="string">或</span> <span class="string">GID。如果未指定，则默认为在Linux上。Windows不支持。</span></span><br><span class="line"><span class="string">mode：在服务的任务容器中安装的文件的权限，以八进制表示法。例如，0444</span> <span class="string">代表文件可读的。默认是</span> <span class="number">0444</span><span class="string">。如果配置文件无法写入，是因为它们安装在临时文件系统中，所以如果设置了可写位，它将被忽略。可执行位可以设置。如果您不熟悉</span> <span class="string">UNIX</span> <span class="string">文件权限模式，Unix</span> <span class="string">Permissions</span> <span class="string">Calculator</span></span><br><span class="line"> </span><br><span class="line"><span class="string">下面示例在容器中将</span> <span class="string">my_config</span> <span class="string">名称设置为</span> <span class="string">redis_config，将模式设置为</span> <span class="number">0440</span><span class="string">（group-readable）并将用户和组设置为</span> <span class="number">103</span><span class="string">。该redis服务无法访问</span> <span class="string">my_other_config</span> <span class="string">配置。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis:latest</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">replicas:1</span></span><br><span class="line">    <span class="attr">configs:</span></span><br><span class="line">      <span class="string">-source:my_config</span></span><br><span class="line">        <span class="string">target:/redis_config</span></span><br><span class="line">        <span class="string">uid:&#x27;103&#x27;</span></span><br><span class="line">        <span class="string">gid:&#x27;103&#x27;</span></span><br><span class="line">        <span class="string">mode:0440</span></span><br><span class="line"><span class="attr">configs:</span></span><br><span class="line">  <span class="attr">my_config:</span></span><br><span class="line">    <span class="string">file:./my_config.txt</span></span><br><span class="line">  <span class="attr">my_other_config:</span></span><br><span class="line">    <span class="string">external:true</span></span><br><span class="line"></span><br><span class="line"><span class="string">可以同时授予多个配置的服务相应的访问权限，也可以混合使用</span> <span class="string">LONG</span> <span class="string">和</span> <span class="string">SHORT</span> <span class="string">语法。定义配置并不意味着授予服务访问权限。</span></span><br><span class="line"><span class="number">2.13</span> <span class="string">container_name</span></span><br><span class="line"><span class="string">为自定义的容器指定一个名称，而不是使用默认的名称</span></span><br><span class="line"><span class="string">container_name:my-web-container</span></span><br><span class="line"><span class="string">由于Docker容器名称必须是唯一的，因此如果指定了自定义名称，则无法将服务扩展到1个容器之外。试图这样做会导致错误。</span></span><br><span class="line"><span class="string">注意：使用（版本3）Compose文件在群集模式下部署堆栈时，将忽略此选项</span> <span class="string">。</span></span><br><span class="line"><span class="number">2.14</span> <span class="string">credential_spec</span></span><br><span class="line"><span class="string">注意：此选项已在v3.3中添加。在Compose版本3.8中支持使用具有撰写文件的组托管服务帐户（GMSA）配置。</span></span><br><span class="line"><span class="string">配置托管服务帐户的凭据规范。此选项仅用于使用Windows容器的服务。</span></span><br><span class="line"><span class="string">使用</span> <span class="string">file时:</span> <span class="string">应该注意引用的文件必须存在于CredentialSpecs,docker</span> <span class="string">数据目录的子目录中。在</span> <span class="string">Windows</span> <span class="string">上，该目录默认为</span> <span class="string">C:\ProgramData\Docker\。以下示例从名为C:\ProgramData\Docker\CredentialSpecs\my-credential-spec.json</span> <span class="string">的文件加载凭证规范</span> <span class="string">：</span></span><br><span class="line"><span class="attr">credential_spec:</span></span><br><span class="line">  <span class="string">file:my-credential-spec.json</span></span><br><span class="line"><span class="string">使用</span> <span class="attr">registry:</span> <span class="string">将从守护进程主机上的</span> <span class="string">Windows</span> <span class="string">注册表中读取凭据规范。其注册表值必须位于：</span></span><br><span class="line"><span class="string">HKLM\SOFTWARE\Microsoft\Windows</span> <span class="string">NT\CurrentVersion\Virtualization\Containers\CredentialSpecs</span></span><br><span class="line"><span class="string">下面的示例通过</span> <span class="string">my-credential-spec</span> <span class="string">注册表中指定的值加载凭证规范：</span></span><br><span class="line"><span class="attr">credential_spec:</span></span><br><span class="line">  <span class="string">registry:my-credential-spec</span></span><br><span class="line"><span class="string">示例GMSA配置</span></span><br><span class="line"><span class="string">为服务配置GMSA凭据规范时，只需指定凭据规范config，如以下示例所示：</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.8&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">myservice:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">myimage:latest</span></span><br><span class="line">    <span class="attr">credential_spec:</span></span><br><span class="line">      <span class="attr">config:</span> <span class="string">my_credential_spec</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">configs:</span></span><br><span class="line">  <span class="attr">my_credentials_spec:</span></span><br><span class="line">    <span class="attr">file:</span> <span class="string">./my-credential-spec.json|</span></span><br><span class="line"><span class="number">2.15</span> <span class="string">depends_on</span></span><br><span class="line"><span class="string">容器中服务之间的依赖关系，依赖关系会导致以下行为：</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">up以依赖顺序启动服务。在以下示例中，db并redis在之前启动web。</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">up</span> <span class="string">SERVICE自动包含SERVICE依赖项。在以下示例中，docker-compose</span> <span class="string">up</span> <span class="string">web还创建并启动db和redis。</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">stop按依赖顺序停止服务。在以下示例中，web在db和之前停止redis</span></span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="string">build:.</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="string">-db</span></span><br><span class="line">      <span class="string">-redis</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="string">image:postgres</span></span><br><span class="line"></span><br><span class="line"><span class="string">使用时需要注意以下几点depends_on：</span></span><br><span class="line"><span class="string">depends_on不会在启动web之前等待db和redis“就绪”</span></span><br><span class="line"><span class="string">版本3不再支持condition形式depends_on。</span></span><br><span class="line"><span class="string">使用版本3</span> <span class="string">Compose文件在swarm模式下部署堆栈depends_on时，将忽略该选项</span> <span class="string">。</span></span><br><span class="line"><span class="number">2.16</span> <span class="string">deploy</span></span><br><span class="line"><span class="string">指定与部署和运行服务相关的配置。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis:alpine</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">replicas:6</span></span><br><span class="line">      <span class="attr">update_config:</span></span><br><span class="line">        <span class="string">parallelism:2</span></span><br><span class="line">        <span class="string">delay:10s</span></span><br><span class="line">      <span class="attr">restart_policy:</span></span><br><span class="line">        <span class="string">condition:on-failure</span></span><br><span class="line"></span><br><span class="line"><span class="string">有几个子选项可供选择：</span></span><br><span class="line"><span class="number">2.16</span><span class="number">.1</span> <span class="string">endpoint_mode</span></span><br><span class="line"><span class="string">endpoint_mode：指定连接到群组外部客户端服务发现方法</span></span><br><span class="line"><span class="string">endpoint_mode:vip</span> <span class="string">：Docker</span> <span class="string">为该服务分配了一个虚拟</span> <span class="string">IP(VIP),作为客户端的</span>  <span class="string">“前端“</span> <span class="string">部位用于访问网络上的服务。</span></span><br><span class="line"><span class="attr">endpoint_mode: dnsrr :</span> <span class="string">DNS轮询（DNSRR）服务发现不使用单个虚拟</span> <span class="string">IP。Docker为服务设置</span> <span class="string">DNS</span> <span class="string">条目，使得服务名称的</span> <span class="string">DNS</span> <span class="string">查询返回一个</span> <span class="string">IP</span> <span class="string">地址列表，并且客户端直接连接到其中的一个。如果想使用自己的负载平衡器，或者混合</span> <span class="string">Windows</span> <span class="string">和</span> <span class="string">Linux</span> <span class="string">应用程序，则</span> <span class="string">DNS</span> <span class="string">轮询调度（round-robin）功能就非常实用。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">wordpress:</span></span><br><span class="line">    <span class="string">image:wordpress</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="string">-&quot;8080:80&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="string">-overlay</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">mode:replicated</span></span><br><span class="line">      <span class="string">replicas:2</span></span><br><span class="line">      <span class="string">endpoint_mode:vip</span></span><br><span class="line"> </span><br><span class="line">  <span class="attr">mysql:</span></span><br><span class="line">    <span class="string">image:mysql</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">       <span class="string">-db-data:/var/lib/mysql/data</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">       <span class="string">-overlay</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">mode:replicated</span></span><br><span class="line">      <span class="string">replicas:2</span></span><br><span class="line">      <span class="string">endpoint_mode:dnsrr</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">db-data:</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">overlay:</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.16</span><span class="number">.2</span> <span class="string">labels</span></span><br><span class="line"><span class="string">指定服务的标签，这些标签仅在服务上设置</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="string">image:web</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="string">com.example.description:&quot;Thislabelwillappearonthewebservice&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">通过将</span> <span class="string">deploy</span> <span class="string">外面的</span> <span class="string">labels</span> <span class="string">标签来设置容器上的</span> <span class="string">labels</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="string">image:web</span></span><br><span class="line">    <span class="attr">labels:</span></span><br><span class="line">      <span class="string">com.example.description:&quot;Thislabelwillappearonallcontainersforthewebservice&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.16</span><span class="number">.3</span> <span class="string">mode</span></span><br><span class="line"><span class="string">global:每个集节点只有一个容器</span></span><br><span class="line"><span class="string">replicated:指定容器数量（默认）</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">worker:</span></span><br><span class="line">    <span class="string">image:dockersamples/examplevotingapp_worker</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">mode:global</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.16</span><span class="number">.4</span> <span class="string">placement</span></span><br><span class="line"><span class="string">指定</span> <span class="string">constraints</span> <span class="string">和</span> <span class="string">preferences</span> </span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="string">image:postgres</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">placement:</span></span><br><span class="line">        <span class="attr">constraints:</span></span><br><span class="line">          <span class="string">-node.role</span> <span class="string">==</span> <span class="string">manager</span></span><br><span class="line">          <span class="string">-engine.labels.operatingsystem</span> <span class="string">==</span> <span class="string">ubuntu</span> <span class="number">14.04</span></span><br><span class="line">        <span class="attr">preferences:</span></span><br><span class="line">          <span class="string">-spread:node.labels.zone</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.16</span><span class="number">.5</span> <span class="string">replicas</span></span><br><span class="line"><span class="string">如果服务是</span> <span class="string">replicated（默认)，需要指定运行的容器数量</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">worker:</span></span><br><span class="line">    <span class="string">image:dockersamples/examplevotingapp_worker</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="string">-frontend</span></span><br><span class="line">      <span class="string">-backend</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">mode:replicated</span></span><br><span class="line">      <span class="string">replicas:6</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.16</span><span class="number">.6</span>  <span class="string">resources</span></span><br><span class="line"><span class="string">配置资源限制</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis:alpine</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">resources:</span></span><br><span class="line">        <span class="attr">limits:</span></span><br><span class="line">          <span class="string">cpus:&#x27;0.50&#x27;</span></span><br><span class="line">          <span class="string">memory:50M</span></span><br><span class="line">        <span class="attr">reservations:</span></span><br><span class="line">          <span class="string">cpus:&#x27;0.25&#x27;</span></span><br><span class="line">          <span class="string">memory:20M</span></span><br><span class="line"></span><br><span class="line"><span class="string">此例子中，redis</span> <span class="string">服务限制使用不超过</span> <span class="string">50M</span> <span class="string">的内存和</span> <span class="number">0.50</span><span class="string">（50％）可用处理时间（CPU），并且保留</span> <span class="string">20M</span> <span class="string">了内存和</span> <span class="number">0.25</span> <span class="string">CPU时间。</span></span><br><span class="line"><span class="number">2.16</span><span class="number">.7</span> <span class="string">restart_policy</span></span><br><span class="line"><span class="string">配置容器的重新启动，代替</span> <span class="string">restart</span></span><br><span class="line"><span class="string">condition：值可以为</span> <span class="string">none</span> <span class="string">、on-failure</span> <span class="string">以及</span> <span class="string">any(默认)</span></span><br><span class="line"><span class="string">delay：尝试重启的等待时间，默认为</span> <span class="number">0</span></span><br><span class="line"><span class="string">max_attempts：在放弃之前尝试重新启动容器次数（默认：从不放弃）。如果重新启动在配置中没有成功</span> <span class="string">window，则此尝试不计入配置max_attempts</span> <span class="string">值。例如，如果</span> <span class="string">max_attempts</span> <span class="string">值为</span> <span class="number">2</span><span class="string">，并且第一次尝试重新启动失败，则可能会尝试重新启动两次以上。</span></span><br><span class="line"><span class="string">windows：在决定重新启动是否成功之前的等时间，指定为持续时间（默认值：立即决定）。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis:alpine</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">restart_policy:</span></span><br><span class="line">        <span class="string">condition:on-failure</span></span><br><span class="line">        <span class="string">delay:5s</span></span><br><span class="line">        <span class="string">max_attempts:3</span></span><br><span class="line">        <span class="string">window:120s</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.16</span><span class="number">.8</span> <span class="string">rollback_config</span></span><br><span class="line"><span class="string">配置在更新失败的情况下应如何回滚服务。</span></span><br><span class="line"><span class="string">parallelism：一次回滚的容器数。如果设置为0，则所有容器同时回滚。</span></span><br><span class="line"><span class="string">delay：每个容器组的回滚之间等待的时间（默认为0）。</span></span><br><span class="line"><span class="string">failure_action：如果回滚失败该怎么办。一个continue或pause（默认pause）</span></span><br><span class="line"><span class="string">monitor：每次更新任务后的持续时间以监视失败(ns|us|ms|s|m|h)（默认为0）。</span></span><br><span class="line"><span class="string">max_failure_ratio：回滚期间容忍的失败率（默认为0）。</span></span><br><span class="line"><span class="string">order：回滚期间的操作顺序。其中一个stop-first（旧任务在启动新任务之前停止），或者start-first（首先启动新任务，并且正在运行的任务暂时重叠）（默认stop-first）。</span></span><br><span class="line"><span class="number">2.16</span><span class="number">.9</span> <span class="string">update_config</span></span><br><span class="line"><span class="string">配置更新服务，用于无缝更新应用（rolling</span> <span class="string">update)</span></span><br><span class="line"><span class="string">parallelism：一次性更新的容器数量</span></span><br><span class="line"><span class="string">delay：更新一组容器之间的等待时间。</span></span><br><span class="line"><span class="string">failure_action：如果更新失败，可以执行的的是</span> <span class="string">continue、rollback</span> <span class="string">或</span> <span class="string">pause</span> <span class="string">（默认）</span></span><br><span class="line"><span class="string">monitor：每次任务更新后监视失败的时间(ns|us|ms|s|m|h)（默认为0）</span></span><br><span class="line"><span class="string">max_failure_ratio：在更新期间能接受的失败率</span></span><br><span class="line"><span class="string">order：更新次序设置，top-first（旧的任务在开始新任务之前停止）、start-first（新的任务首先启动，并且正在运行的任务短暂重叠）（默认</span> <span class="string">stop-first）</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">vote:</span></span><br><span class="line">    <span class="string">image:dockersamples/examplevotingapp_vote:before</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="string">-redis</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">replicas:2</span></span><br><span class="line">      <span class="attr">update_config:</span></span><br><span class="line">        <span class="string">parallelism:2</span></span><br><span class="line">        <span class="string">delay:10s</span></span><br><span class="line">        <span class="string">order:stop-first</span></span><br><span class="line"></span><br><span class="line"><span class="string">不支持</span> <span class="string">Docker</span> <span class="string">stack</span> <span class="string">desploy</span> <span class="string">的几个子选项</span> </span><br><span class="line"><span class="string">build、cgroup_parent、container_name、devices、tmpfs、external_links、inks、network_mode、restart、security_opt、stop_signal、sysctls、userns_mode</span></span><br><span class="line"><span class="number">2.17</span> <span class="string">devices</span></span><br><span class="line"><span class="string">设置映射列表，与</span> <span class="string">Docker</span> <span class="string">客户端的</span> <span class="string">--device</span> <span class="string">参数类似</span></span><br><span class="line"></span><br><span class="line"><span class="attr">devices:</span></span><br><span class="line">  <span class="string">-&quot;/dev/ttyUSB0:/dev/ttyUSB0&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.18</span> <span class="string">dns</span></span><br><span class="line"><span class="string">自定义</span> <span class="string">DNS</span> <span class="string">服务器，与</span> <span class="string">--dns</span> <span class="string">具有一样的用途，可以是单个值或列表</span></span><br><span class="line"></span><br><span class="line"><span class="string">dns:8.8.8.8</span></span><br><span class="line"><span class="attr">dns:</span></span><br><span class="line">  <span class="number">-8.8</span><span class="number">.8</span><span class="number">.8</span></span><br><span class="line">  <span class="number">-9.9</span><span class="number">.9</span><span class="number">.9</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.19</span> <span class="string">dns_search</span></span><br><span class="line"><span class="string">自定义</span> <span class="string">DNS</span> <span class="string">搜索域，可以是单个值或列表</span></span><br><span class="line"></span><br><span class="line"><span class="string">dns_search:example.com</span></span><br><span class="line"><span class="attr">dns_search:</span></span><br><span class="line">  <span class="string">-dc1.example.com</span></span><br><span class="line">  <span class="string">-dc2.example.com</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.20</span> <span class="string">entrypoint</span></span><br><span class="line"><span class="string">在</span> <span class="string">Dockerfile</span> <span class="string">中有一个指令叫做</span> <span class="string">ENTRYPOINT</span> <span class="string">指令，用于指定接入点。在</span> <span class="string">docker-compose.yml</span> <span class="string">中可以定义接入点，覆盖</span> <span class="string">Dockerfile</span> <span class="string">中的定义</span></span><br><span class="line"></span><br><span class="line"><span class="string">entrypoint:/code/entrypoint.sh</span></span><br><span class="line"><span class="string">入口点也可以是一个列表，方式类似于</span> <span class="string">dckerfile：</span></span><br><span class="line"><span class="attr">entrypoint:</span></span><br><span class="line">    <span class="string">-php</span></span><br><span class="line">    <span class="string">--d</span></span><br><span class="line">    <span class="string">-zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so</span></span><br><span class="line">    <span class="string">--d</span></span><br><span class="line">    <span class="string">-memory_limit=-1</span></span><br><span class="line">    <span class="string">-vendor/bin/phpunit</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.21</span> <span class="string">env_file</span></span><br><span class="line"><span class="string">从文件中添加环境变量。可以是单个值或是列表</span> </span><br><span class="line"><span class="string">如果已经用</span> <span class="string">docker-compose</span> <span class="string">-f</span> <span class="string">FILE</span> <span class="string">指定了</span> <span class="string">Compose</span> <span class="string">文件，那么</span> <span class="string">env_file</span> <span class="string">路径值为相对于该文件所在的目录</span></span><br><span class="line"><span class="string">但</span> <span class="string">environment</span> <span class="string">环境中的设置的变量会会覆盖这些值，无论这些值未定义还是为</span> <span class="string">None</span></span><br><span class="line"><span class="string">env_file:.env</span></span><br><span class="line"><span class="attr">env_file:</span></span><br><span class="line">  <span class="string">-./common.env</span></span><br><span class="line">  <span class="string">-./apps/web.env</span></span><br><span class="line">  <span class="string">-/opt/secrets.env</span></span><br><span class="line"><span class="string">环境配置文件</span> <span class="string">env_file</span> <span class="string">中的声明每行都是以</span> <span class="string">VAR=VAL</span> <span class="string">格式，其中以</span> <span class="comment"># 开头的被解析为注释而被忽略，注意环境变量配置列表的顺序，例如下面例子</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">some-service:</span></span><br><span class="line">    <span class="attr">env_file:</span></span><br><span class="line">      <span class="string">-a.env</span></span><br><span class="line">      <span class="string">-b.env</span></span><br><span class="line"><span class="string">a.env</span> <span class="string">文件</span></span><br><span class="line"><span class="comment"># a.env</span></span><br><span class="line"><span class="string">VAR=1</span></span><br><span class="line"><span class="string">b.env文件</span></span><br><span class="line"><span class="comment"># b.env</span></span><br><span class="line"><span class="string">VAR=hello</span></span><br><span class="line"></span><br><span class="line"><span class="string">对于在文件a.env</span> <span class="string">中指定的相同变量但在文件</span> <span class="string">b.env</span> <span class="string">中分配了不同的值，如果</span> <span class="string">b.env</span> <span class="string">像下面列在</span> <span class="string">a.env</span> <span class="string">之后，则刚在</span> <span class="string">a.env</span> <span class="string">设置的值被</span> <span class="string">b.env</span> <span class="string">相同变量的值覆盖，此时</span> <span class="string">$VAR</span> <span class="string">值为</span> <span class="string">hello。此外，这里所说的环境变量是对宿主机的</span> <span class="string">Compose</span> <span class="string">而言的，如果在配置文件中有</span> <span class="string">build</span> <span class="string">操作，这些变量并不会进入构建过程中，如果要在构建中使用变量还是首选</span> <span class="string">arg</span> <span class="string">标签。</span></span><br><span class="line"><span class="number">2.22</span> <span class="string">environment</span></span><br><span class="line"><span class="string">添加环境变量，可以使用数组或字典。与上面的</span> <span class="string">env_file</span> <span class="string">选项完全不同，反而和</span> <span class="string">arg</span> <span class="string">有几分类似，这个标签的作用是设置镜像变量，它可以保存变量到镜像里面，也就是说启动的容器也会包含这些变量设置，这是与</span> <span class="string">arg</span> <span class="string">最大的不同。</span> </span><br><span class="line"><span class="string">一般</span> <span class="string">arg</span> <span class="string">标签的变量仅用在构建过程中。而</span> <span class="string">environment</span> <span class="string">和</span> <span class="string">Dockerfile</span> <span class="string">中的</span> <span class="string">ENV</span> <span class="string">指令一样会把变量一直保存在镜像、容器中，类似</span> <span class="string">docker</span> <span class="string">run</span> <span class="string">-e</span> <span class="string">的效果。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">environment:</span></span><br><span class="line">  <span class="string">RACK_ENV:development</span></span><br><span class="line">  <span class="string">SHOW:&#x27;true&#x27;</span></span><br><span class="line">  <span class="attr">SESSION_SECRET:</span></span><br><span class="line"><span class="attr">environment:</span></span><br><span class="line">  <span class="string">-RACK_ENV=development</span></span><br><span class="line">  <span class="string">-SHOW=true</span></span><br><span class="line">  <span class="string">-SESSION_SECRET</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.23</span> <span class="string">expose</span></span><br><span class="line"><span class="string">暴露端口，但不映射到宿主机，只被连接的服务访问。这个标签与</span> <span class="string">Dockerfile</span> <span class="string">中的</span> <span class="string">EXPOSE</span> <span class="string">指令一样，用于指定暴露的端口，但是只是作为一种参考，实际上</span> <span class="string">docker-compose.yml</span> <span class="string">的端口映射还得</span> <span class="string">ports</span> <span class="string">这样的标签</span></span><br><span class="line"></span><br><span class="line"><span class="attr">expose:</span></span><br><span class="line"> <span class="string">-&quot;3000&quot;</span></span><br><span class="line"> <span class="string">-&quot;8000&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.24</span> <span class="string">external_links</span></span><br><span class="line"><span class="string">链接到</span> <span class="string">docker-compose.yml</span> <span class="string">外部的容器，甚至并非</span> <span class="string">Compose</span> <span class="string">项目文件管理的容器。参数格式跟</span> <span class="string">links</span> <span class="string">类似。</span></span><br><span class="line"><span class="string">在使用Docker过程中，会有许多单独使用</span> <span class="string">docker</span> <span class="string">run</span> <span class="string">启动的容器的情况，为了使</span> <span class="string">Compose</span> <span class="string">能够连接这些不在docker-compose.yml</span> <span class="string">配置文件中定义的容器，那么就需要一个特殊的标签，就是</span> <span class="string">external_links，它可以让Compose</span> <span class="string">项目里面的容器连接到那些项目配置外部的容器（前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面）。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">external_links:</span></span><br><span class="line"> <span class="string">-redis_1</span></span><br><span class="line"> <span class="string">-project_db_1:mysql</span></span><br><span class="line"> <span class="string">-project_db_1:postgresql</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.25</span> <span class="string">extra_hosts</span></span><br><span class="line"><span class="string">添加主机名的标签，就是往</span> <span class="string">/etc/hosts</span> <span class="string">文件中添加一些记录，与</span> <span class="string">Docker</span> <span class="string">客户端中的</span> <span class="string">--add-host</span> <span class="string">类似。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">extra_hosts:</span></span><br><span class="line"> <span class="string">-&quot;somehost:162.242.195.82&quot;</span></span><br><span class="line"> <span class="string">-&quot;otherhost:50.31.209.229&quot;</span></span><br><span class="line"><span class="string">在/etc/hosts此服务的内部容器中创建具有ip地址和主机名的条目，例如：</span></span><br><span class="line"><span class="number">162.242</span><span class="number">.195</span><span class="number">.82</span>  <span class="string">somehost</span></span><br><span class="line"><span class="number">50.31</span><span class="number">.209</span><span class="number">.229</span>   <span class="string">otherhost</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.26</span> <span class="string">healthcheck</span></span><br><span class="line"><span class="string">用于检查测试服务使用的容器是否正常</span></span><br><span class="line"></span><br><span class="line"><span class="attr">healthcheck:</span></span><br><span class="line">  <span class="string">test:[&quot;CMD&quot;,&quot;curl&quot;,&quot;-f&quot;,&quot;http://localhost&quot;]</span></span><br><span class="line">  <span class="string">interval:1m30s</span></span><br><span class="line">  <span class="string">timeout:10s</span></span><br><span class="line">  <span class="string">retries:3</span></span><br><span class="line">  <span class="string">start_period:40s</span></span><br><span class="line"></span><br><span class="line"><span class="string">interval，timeout</span> <span class="string">以及</span> <span class="string">start_period</span> <span class="string">都定为持续时间</span></span><br><span class="line"><span class="string">test</span> <span class="string">必须是字符串或列表，如果它是一个列表，第一项必须是</span> <span class="string">NONE，CMD</span> <span class="string">或</span> <span class="string">CMD-SHELL</span> <span class="string">；如果它是一个字符串，则相当于指定CMD-SHELL</span> <span class="string">后跟该字符串。</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Hit the local web app</span></span><br><span class="line"><span class="string">test:[&quot;CMD&quot;,&quot;curl&quot;,&quot;-f&quot;,&quot;http://localhost&quot;]</span></span><br><span class="line"><span class="string">如上所述，但包裹在内/bin/sh。以下两种形式都是等同的。</span></span><br><span class="line"><span class="string">test:[&quot;CMD-SHELL&quot;,&quot;curl-fhttp://localhost||exit1&quot;]</span></span><br><span class="line"><span class="string">test:curl</span> <span class="string">-f</span> <span class="string">https://localhost</span> <span class="string">||</span> <span class="string">exit</span> <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="string">果需要禁用镜像的所有检查项目，可以使用</span> <span class="string">disable:true,相当于</span> <span class="string">test:[&quot;NONE&quot;]</span></span><br><span class="line"></span><br><span class="line"><span class="attr">healthcheck:</span></span><br><span class="line">  <span class="string">disable:true</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.27</span> <span class="string">image</span></span><br><span class="line"><span class="string">从指定的镜像中启动容器，可以是存储仓库、标签以及镜像</span> <span class="string">ID。</span></span><br><span class="line"><span class="string">如果镜像不存在，Compose</span> <span class="string">会自动拉去镜像。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">image:</span> <span class="string">redis</span></span><br><span class="line"><span class="attr">image:</span> <span class="string">ubuntu:14.04</span></span><br><span class="line"><span class="attr">image:</span> <span class="string">tutum/influxdb</span></span><br><span class="line"><span class="attr">image:</span> <span class="string">example-registry.com:4000/postgresql</span></span><br><span class="line"><span class="attr">image:</span> <span class="string">a4bc65fd</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.28</span> <span class="string">init</span></span><br><span class="line"><span class="string">在容器内运行init，转发信号并重新获得进程。将此选项设置true是为服务启用此功能。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="string">image:alpine:latest</span></span><br><span class="line">    <span class="string">init:true</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.29</span>  <span class="string">isolation</span></span><br><span class="line"><span class="string">指定容器的隔离技术。在Linux上，唯一支持的值是default。在Windows中，可接受的值是default，process和</span> <span class="string">hyperv。</span></span><br><span class="line"><span class="number">2.30</span> <span class="string">links</span></span><br><span class="line"><span class="string">链接到其它服务的中的容器，可以指定服务名称也可以指定链接别名（SERVICE：ALIAS)，与</span> <span class="string">Docker</span> <span class="string">客户端的</span> <span class="string">--link</span> <span class="string">有一样效果，会连接到其它服务中的容器。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">web:</span></span><br><span class="line">  <span class="attr">links:</span></span><br><span class="line">   <span class="string">-db</span></span><br><span class="line">   <span class="string">-db:database</span></span><br><span class="line">   <span class="string">-redis</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.31</span> <span class="string">logging</span></span><br><span class="line"><span class="string">配置日志服务</span></span><br><span class="line"></span><br><span class="line"><span class="attr">logging:</span></span><br><span class="line">  <span class="string">driver:syslog</span></span><br><span class="line">  <span class="attr">options:</span></span><br><span class="line">    <span class="string">syslog-address:&quot;tcp://192.168.0.42:123&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">该</span> <span class="string">driver值是指定服务器的日志记录驱动程序，默认值为</span> <span class="string">json-file,与</span> <span class="string">--log-diver</span> <span class="string">选项一样</span> </span><br><span class="line"></span><br><span class="line"><span class="attr">driver:</span> <span class="string">&quot;json-file&quot;</span></span><br><span class="line"><span class="attr">driver:</span> <span class="string">&quot;syslog&quot;</span></span><br><span class="line"><span class="attr">driver:</span> <span class="string">&quot;none&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">注意：只有驱动程序</span> <span class="string">json-file</span> <span class="string">和</span> <span class="string">journald</span> <span class="string">驱动程序可以直接从</span> <span class="string">docker-compose</span> <span class="string">up</span> <span class="string">和</span> <span class="string">docker-compose</span> <span class="string">logs</span> <span class="string">获取日志。使用任何其他方式不会显示任何日志。</span></span><br><span class="line"><span class="string">对于可选值，可以使用</span> <span class="string">options</span> <span class="string">指定日志记录中的日志记录选项</span></span><br><span class="line"></span><br><span class="line"><span class="string">driver:&quot;syslog&quot;</span></span><br><span class="line"><span class="attr">options:</span></span><br><span class="line">  <span class="string">syslog-address:&quot;tcp://192.168.0.42:123&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">默认驱动程序</span> <span class="string">json-file</span>  <span class="string">具有限制存储日志量的选项，所以，使用键值对来获得最大存储大小以及最小存储数量</span></span><br><span class="line"></span><br><span class="line"><span class="attr">driver:</span> <span class="string">&quot;json-file&quot;</span></span><br><span class="line"><span class="attr">options:</span></span><br><span class="line">  <span class="string">max-size:&quot;200k&quot;</span></span><br><span class="line">  <span class="string">max-file:&quot;10&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">上面实例将存储日志文件，直到它们达到max-size:200kB，存储的单个日志文件的数量由该</span> <span class="string">max-file</span> <span class="string">值指定。随着日志增长超出最大限制，旧日志文件将被删除以存储新日志</span></span><br><span class="line"><span class="string">docker-compose.yml</span> <span class="string">限制日志存储的示例</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">some-service:</span></span><br><span class="line">    <span class="string">image:some-service</span></span><br><span class="line">    <span class="attr">logging:</span></span><br><span class="line">      <span class="string">driver:&quot;json-file&quot;</span></span><br><span class="line">      <span class="attr">options:</span></span><br><span class="line">        <span class="string">max-size:&quot;200k&quot;</span></span><br><span class="line">        <span class="string">max-file:&quot;10&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.32</span> <span class="string">network_mode</span></span><br><span class="line"><span class="string">网络模式，用法类似于</span> <span class="string">Docke</span> <span class="string">客户端的</span> <span class="string">--net</span> <span class="string">选项，格式为：service:[service</span> <span class="string">name]，可以指定使用服务或者容器的网络。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">network_mode:</span> <span class="string">&quot;bridge&quot;</span></span><br><span class="line"><span class="attr">network_mode:</span> <span class="string">&quot;host&quot;</span></span><br><span class="line"><span class="attr">network_mode:</span> <span class="string">&quot;none&quot;</span></span><br><span class="line"><span class="attr">network_mode:</span> <span class="string">&quot;service:[service name]&quot;</span></span><br><span class="line"><span class="attr">network_mode:</span> <span class="string">&quot;container:[container name/id]&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.33</span> <span class="string">networks</span></span><br><span class="line"><span class="string">加入指定网络</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">some-service:</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">     <span class="string">-some-network</span></span><br><span class="line">     <span class="string">-other-network</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.34</span> <span class="string">aliases</span></span><br><span class="line"><span class="string">同一网络上的其他容器可以使用服务器名称或别名来连接到其他服务的容器，相同的服务可以在不同的网络有不同的别名。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">some-service:</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="attr">some-network:</span></span><br><span class="line">        <span class="attr">aliases:</span></span><br><span class="line">         <span class="string">-alias1</span></span><br><span class="line">         <span class="string">-alias3</span></span><br><span class="line">      <span class="attr">other-network:</span></span><br><span class="line">        <span class="attr">aliases:</span></span><br><span class="line">         <span class="string">-alias2</span></span><br><span class="line"></span><br><span class="line"><span class="string">下面实例中，提供</span> <span class="string">web</span> <span class="string">、worker以及db</span> <span class="string">服务，伴随着两个网络</span> <span class="string">new</span> <span class="string">和</span> <span class="string">legacy</span> <span class="string">。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="string">image:&quot;nginx:alpine&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="string">-new</span></span><br><span class="line"> </span><br><span class="line">  <span class="attr">worker:</span></span><br><span class="line">    <span class="string">image:&quot;my-worker-image:latest&quot;</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="string">-legacy</span></span><br><span class="line"> </span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="string">image:mysql</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="attr">new:</span></span><br><span class="line">        <span class="attr">aliases:</span></span><br><span class="line">          <span class="string">-database</span></span><br><span class="line">      <span class="attr">legacy:</span></span><br><span class="line">        <span class="attr">aliases:</span></span><br><span class="line">          <span class="string">-mysql</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">new:</span></span><br><span class="line">  <span class="attr">legacy:</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.35</span> <span class="string">ipv4_address、ipv6_address</span></span><br><span class="line"><span class="string">为服务的容器指定一个静态</span> <span class="string">IP</span> <span class="string">地址</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app:</span></span><br><span class="line">    <span class="string">image:nginx:alpine</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="attr">app_net:</span></span><br><span class="line">        <span class="string">ipv4_address:172.16.238.10</span></span><br><span class="line">        <span class="string">ipv6_address:2001:3984:3989::10</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">app_net:</span></span><br><span class="line">    <span class="attr">ipam:</span></span><br><span class="line">      <span class="string">driver:default</span></span><br><span class="line">      <span class="attr">config:</span></span><br><span class="line">        <span class="string">-subnet:&quot;172.16.238.0/24&quot;</span></span><br><span class="line">        <span class="string">-subnet:&quot;2001:3984:3989::/64&quot;</span></span><br><span class="line"><span class="number">2.36</span> <span class="string">pid</span></span><br><span class="line"></span><br><span class="line"><span class="string">将</span> <span class="string">PID</span> <span class="string">模式设置为主机</span> <span class="string">PID</span> <span class="string">模式，可以打开容器与主机操作系统之间的共享</span> <span class="string">PID</span> <span class="string">地址空间。使用此标志启动的容器可以访问和操作宿主机的其他容器，反之亦然</span></span><br><span class="line"><span class="attr">pid:</span> <span class="string">&quot;host&quot;</span></span><br><span class="line"><span class="number">2.37</span> <span class="string">ports</span></span><br><span class="line"><span class="string">映射端口</span></span><br><span class="line"><span class="number">2.37</span><span class="number">.1</span> <span class="string">SHORT</span> <span class="string">语法</span></span><br><span class="line"><span class="string">可以使用</span> <span class="string">HOST:CONTAINER</span> <span class="string">的方式指定端口，也可以指定容器端口（选择临时主机端口），宿主机会随机映射端口</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ports:</span></span><br><span class="line"> <span class="string">-&quot;3000&quot;</span></span><br><span class="line"> <span class="string">-&quot;3000-3005&quot;</span></span><br><span class="line"> <span class="string">-&quot;8000:8000&quot;</span></span><br><span class="line"> <span class="string">-&quot;9090-9091:8080-8081&quot;</span></span><br><span class="line"> <span class="string">-&quot;49100:22&quot;</span></span><br><span class="line"> <span class="string">-&quot;127.0.0.1:8001:8001&quot;</span></span><br><span class="line"> <span class="string">-&quot;127.0.0.1:5000-5010:5000-5010&quot;</span></span><br><span class="line"> <span class="string">-&quot;6060:6060/udp&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">注意：当使用</span> <span class="string">HOST:CONTAINER</span> <span class="string">格式来映射端口时，如果使用的容器端口小于60可能会得到错误得结果，因为YAML</span> <span class="string">将会解析</span> <span class="string">xx:yy</span> <span class="string">这种数字格式为</span> <span class="number">60</span> <span class="string">进制，所以建议采用字符串格式。</span></span><br><span class="line"><span class="number">2.37</span><span class="number">.3</span> <span class="string">LONG语法</span></span><br><span class="line"><span class="string">LONG</span> <span class="string">语法支持</span> <span class="string">SHORT</span> <span class="string">语法不支持的附加字段</span></span><br><span class="line"><span class="string">target：容器内的端口</span></span><br><span class="line"><span class="string">published：公开的端口</span></span><br><span class="line"><span class="string">protocol：</span>  <span class="string">端口协议（tcp</span> <span class="string">或</span> <span class="string">udp）</span></span><br><span class="line"><span class="string">mode：通过host</span> <span class="string">用在每个节点还是哪个发布的主机端口或使用</span> <span class="string">ingress</span> <span class="string">用于集群模式端口进行平衡负载</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ports:</span></span><br><span class="line">  <span class="string">-target:80</span></span><br><span class="line">    <span class="string">published:8080</span></span><br><span class="line">    <span class="string">protocol:tcp</span></span><br><span class="line">    <span class="string">mode:host</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.38</span> <span class="string">restart</span></span><br><span class="line"><span class="string">默认值为</span> <span class="literal">no</span> <span class="string">，即在任何情况下都不会重新启动容器；当值为</span> <span class="string">always</span> <span class="string">时，容器总是重新启动；当值为</span> <span class="string">on-failure</span> <span class="string">时，当出现</span> <span class="string">on-failure</span> <span class="string">报错容器退出时，容器重新启动。</span></span><br><span class="line"></span><br><span class="line"><span class="attr">restart:</span> <span class="string">&quot;no&quot;</span></span><br><span class="line"><span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line"><span class="attr">restart:</span> <span class="string">on-failure</span></span><br><span class="line"><span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.40</span> <span class="string">secrets</span></span><br><span class="line"><span class="string">通过</span> <span class="string">secrets为每个服务授予相应的访问权限</span></span><br><span class="line"><span class="number">2.40</span><span class="number">.1</span> <span class="string">SHORT语法</span></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis:latest</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">replicas:1</span></span><br><span class="line">    <span class="attr">secrets:</span></span><br><span class="line">      <span class="string">-my_secret</span></span><br><span class="line">      <span class="string">-my_other_secret</span></span><br><span class="line"><span class="attr">secrets:</span></span><br><span class="line">  <span class="attr">my_secret:</span></span><br><span class="line">    <span class="string">file:./my_secret.txt</span></span><br><span class="line">  <span class="attr">my_other_secret:</span></span><br><span class="line">    <span class="string">external:true</span></span><br><span class="line"><span class="number">2.40</span><span class="number">.2</span> <span class="string">LONG语法</span></span><br><span class="line"><span class="string">LONG</span> <span class="string">语法可以添加其他选项</span></span><br><span class="line"><span class="string">source：secret</span> <span class="string">名称</span></span><br><span class="line"><span class="string">target：在服务任务容器中需要装载在</span> <span class="string">/run/secrets/</span> <span class="string">中的文件名称，如果</span> <span class="string">source</span> <span class="string">未定义，那么默认为此值</span></span><br><span class="line"><span class="string">uid&amp;gid：在服务的任务容器中拥有该文件的</span> <span class="string">UID</span> <span class="string">或</span> <span class="string">GID</span> <span class="string">。如果未指定，两者都默认为</span> <span class="number">0</span><span class="string">。</span></span><br><span class="line"><span class="string">mode：以八进制表示法将文件装载到服务的任务容器中</span> <span class="string">/run/secrets/</span> <span class="string">的权限。例如，0444</span> <span class="string">代表可读。</span></span><br><span class="line"></span><br><span class="line"><span class="string">version:&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="string">image:redis:latest</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="string">replicas:1</span></span><br><span class="line">    <span class="attr">secrets:</span></span><br><span class="line">      <span class="string">-source:my_secret</span></span><br><span class="line">        <span class="string">target:redis_secret</span></span><br><span class="line">        <span class="string">uid:&#x27;103&#x27;</span></span><br><span class="line">        <span class="string">gid:&#x27;103&#x27;</span></span><br><span class="line">        <span class="string">mode:0440</span></span><br><span class="line"><span class="attr">secrets:</span></span><br><span class="line">  <span class="attr">my_secret:</span></span><br><span class="line">    <span class="string">file:./my_secret.txt</span></span><br><span class="line">  <span class="attr">my_other_secret:</span></span><br><span class="line">    <span class="string">external:true</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.41</span> <span class="string">security_opt</span></span><br><span class="line"><span class="string">为每个容器覆盖默认的标签。简单说来就是管理全部服务的标签，比如设置全部服务的</span> <span class="string">user</span> <span class="string">标签值为</span> <span class="string">USER</span></span><br><span class="line"></span><br><span class="line"><span class="attr">security_opt:</span></span><br><span class="line">  <span class="string">-label:user:USER</span></span><br><span class="line">  <span class="string">-label:role:ROLE</span></span><br><span class="line"><span class="number">2.42</span> <span class="string">stop_grace_period</span></span><br><span class="line"><span class="string">在发送</span> <span class="string">SIGKILL</span> <span class="string">之前指定</span> <span class="string">stop_signal</span> <span class="string">，如果试图停止容器（如果它没有处理</span> <span class="string">SIGTERM（或指定的任何停止信号）），则需要等待的时间</span> </span><br><span class="line"></span><br><span class="line"><span class="attr">stop_grace_period:</span> <span class="string">1s</span></span><br><span class="line"><span class="attr">stop_grace_period:</span> <span class="string">1m30s</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.43</span> <span class="string">stop_signal</span></span><br><span class="line"><span class="string">设置另一个信号来停止容器。在默认情况下使用的</span> <span class="string">SIGTERM</span> <span class="string">来停止容器。设置另一个信号可以使用</span> <span class="string">stop_signal</span> <span class="string">标签：</span></span><br><span class="line"></span><br><span class="line"><span class="string">stop_signal:SIGUSR1</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.44</span> <span class="string">sysctls</span></span><br><span class="line"><span class="string">在容器中设置的内核参数，可以为数组或字典</span></span><br><span class="line"><span class="attr">sysctls:</span></span><br><span class="line">  <span class="attr">net.core.somaxconn:</span> <span class="number">1024</span></span><br><span class="line">  <span class="attr">net.ipv4.tcp_syncookies:</span> <span class="number">0</span></span><br><span class="line"><span class="attr">sysctls:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">net.core.somaxconn=1024</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">net.ipv4.tcp_syncookies=0</span></span><br><span class="line"></span><br><span class="line"><span class="string">注意：使用（版本3）Compose文件在群集模式下部署堆栈时，将忽略此选项</span></span><br><span class="line"><span class="number">2.45</span> <span class="string">tmpfs</span></span><br><span class="line"><span class="string">挂载临时文件目录到容器内部，与</span> <span class="string">run</span> <span class="string">的参数一样效果，可以是单个值或列表</span></span><br><span class="line"><span class="attr">tmpfs:</span> <span class="string">/run</span></span><br><span class="line"><span class="attr">tmpfs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">/run</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">/tmp</span></span><br><span class="line"><span class="string">在容器内安装临时文件系统。Size参数指定tmpfs</span> <span class="string">mount的大小（以字节为单位）。默认无限制。</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">type:</span> <span class="string">tmpfs</span></span><br><span class="line">     <span class="attr">target:</span> <span class="string">/app</span></span><br><span class="line">     <span class="attr">tmpfs:</span></span><br><span class="line">       <span class="attr">size:</span> <span class="number">1000</span></span><br><span class="line"></span><br><span class="line"><span class="string">注意：使用（版本3-3.5）Compose文件在群集模式下部署堆栈时，将忽略此选项</span>  <span class="string">。</span></span><br><span class="line"><span class="number">2.46</span> <span class="string">ulimits</span></span><br><span class="line"><span class="string">覆盖容器的默认限制，可以单一地将限制值设为一个整数，也可以将soft/hard</span> <span class="string">限制指定为映射</span></span><br><span class="line"><span class="attr">ulimits:</span></span><br><span class="line">  <span class="attr">nproc:</span> <span class="number">65535</span></span><br><span class="line">  <span class="attr">nofile:</span></span><br><span class="line">    <span class="attr">soft:</span> <span class="number">20000</span></span><br><span class="line">    <span class="attr">hard:</span> <span class="number">40000</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.47</span> <span class="string">userns_mode</span></span><br><span class="line"><span class="string">如果Docker守护程序配置了用户名称空间，则禁用此服务的用户名称空间。</span></span><br><span class="line"><span class="attr">userns_mode:</span> <span class="string">&quot;host&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">注意：使用（版本3）Compose文件在群集模式下部署堆栈时，将忽略此选项</span> <span class="string">。</span></span><br><span class="line"><span class="number">2.48</span>  <span class="string">volumes</span></span><br><span class="line"><span class="string">挂载一个目录或者一个已存在的数据卷容器，可以直接使用</span> <span class="string">HOST:CONTAINER</span> <span class="string">这样的格式，或者使用</span> <span class="string">HOST:CONTAINER:ro</span> <span class="string">这样的格式，后者对于容器来说，数据卷是只读的，这样可以有效保护宿主机的文件系统</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:alpine</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">volume</span></span><br><span class="line">        <span class="attr">source:</span> <span class="string">mydata</span></span><br><span class="line">        <span class="attr">target:</span> <span class="string">/data</span></span><br><span class="line">        <span class="attr">volume:</span></span><br><span class="line">          <span class="attr">nocopy:</span> <span class="literal">true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">bind</span></span><br><span class="line">        <span class="attr">source:</span> <span class="string">./static</span></span><br><span class="line">        <span class="attr">target:</span> <span class="string">/opt/app/static</span></span><br><span class="line"> </span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres:latest</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;dbdata:/var/lib/postgresql/data&quot;</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">mydata:</span></span><br><span class="line">  <span class="attr">dbdata:</span></span><br><span class="line"></span><br><span class="line"><span class="string">此示例显示服务使用的命名卷（mydata）web以及为单个服务（db服务</span> <span class="string">下的第一个路径volumes）定义的绑定安装。该db服务还使用名为dbdata（db服务中的第二个路径volumes）的命名卷，但使用旧字符串格式定义它以安装命名卷。必须在顶级volumes键下列出命名卷</span></span><br><span class="line"><span class="number">2.48</span><span class="number">.1</span> <span class="string">SHORT语法</span></span><br><span class="line"><span class="string">可以选择在主机（HOST:CONTAINER）或访问模式（HOST:CONTAINER:ro）上指定路径。</span></span><br><span class="line"><span class="string">可以在主机上挂载相对路径，该路径相对于正在使用的</span> <span class="string">Compose</span> <span class="string">配置文件的目录进行扩展。相对路径应始终以</span> <span class="string">.</span> <span class="string">或</span> <span class="string">..</span> <span class="string">开头</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="comment"># Just specify a path and let the Engine create a volume</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">/var/lib/mysql</span></span><br><span class="line"> </span><br><span class="line">  <span class="comment"># Specify an absolute path mapping</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">/opt/data:/var/lib/mysql</span></span><br><span class="line"> </span><br><span class="line">  <span class="comment"># Path on the host, relative to the Compose file</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">./cache:/tmp/cache</span></span><br><span class="line"> </span><br><span class="line">  <span class="comment"># User-relative path</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">~/configs:/etc/configs/:ro</span></span><br><span class="line"> </span><br><span class="line">  <span class="comment"># Named volume</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">datavolume:/var/lib/mysql</span></span><br><span class="line"><span class="number">2.48</span><span class="number">.2</span> <span class="string">LONG语法</span></span><br><span class="line"><span class="string">LONG</span> <span class="string">语法有些附加字段</span></span><br><span class="line"><span class="string">type：安装类型，可以为</span> <span class="string">volume、bind</span> <span class="string">或</span> <span class="string">tmpfs</span></span><br><span class="line"><span class="string">source：安装源，主机上用于绑定安装的路径或定义在顶级</span> <span class="string">volumes密钥中卷的名称</span> <span class="string">,不适用于</span> <span class="string">tmpfs</span> <span class="string">类型安装。</span></span><br><span class="line"><span class="string">target：卷安装在容器中的路径</span></span><br><span class="line"><span class="string">read_only：标志将卷设置为只读</span></span><br><span class="line"><span class="string">bind：配置额外的绑定选项</span></span><br><span class="line"><span class="string">propagation：用于绑定的传播模式</span></span><br><span class="line"><span class="string">volume：配置其他卷选项</span></span><br><span class="line"><span class="string">nocopy：创建卷时禁止从容器复制数据的标志</span></span><br><span class="line"><span class="string">tmpfs：配置额外的</span> <span class="string">tmpfs</span> <span class="string">选项</span></span><br><span class="line"><span class="string">size：tmpfs</span> <span class="string">的大小，以字节为单位</span></span><br><span class="line"><span class="string">consistent：完全一致。容器运行时和主机始终保持相同的安装视图。这是默认值。</span></span><br><span class="line"><span class="string">cached：主机的mount视图是权威的。在主机上进行的更新在容器中可见之前可能会有延迟。</span></span><br><span class="line"><span class="string">delegated：容器运行时的mount视图是权威的。在容器中进行的更新在主机上可见之前可能会有延迟。</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">web:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">nginx:alpine</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;80:80&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">volume</span></span><br><span class="line">        <span class="attr">source:</span> <span class="string">mydata</span></span><br><span class="line">        <span class="attr">target:</span> <span class="string">/data</span></span><br><span class="line">        <span class="attr">volume:</span></span><br><span class="line">          <span class="attr">nocopy:</span> <span class="literal">true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">bind</span></span><br><span class="line">        <span class="attr">source:</span> <span class="string">./static</span></span><br><span class="line">        <span class="attr">target:</span> <span class="string">/opt/app/static</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">webnet:</span></span><br><span class="line"> </span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">mydata:</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.49</span> <span class="string">driver</span></span><br><span class="line"><span class="string">指定应为此卷使用哪个卷驱动程序。默认为Docker</span> <span class="string">Engine配置使用的任何驱动程序，在大多数情况下是local。如果驱动程序不可用，则在docker-compose</span> <span class="string">up尝试创建卷时Engine会返回错误</span> <span class="string">。</span></span><br><span class="line"><span class="attr">driver:</span> <span class="string">foobar</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.50</span> <span class="string">driver_opts</span></span><br><span class="line"><span class="string">将选项列表指定为键值对，以传递给此卷的驱动程序。</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">example:</span></span><br><span class="line">    <span class="attr">driver_opts:</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">&quot;nfs&quot;</span></span><br><span class="line">      <span class="attr">o:</span> <span class="string">&quot;addr=10.40.0.199,nolock,soft,rw&quot;</span></span><br><span class="line">      <span class="attr">device:</span> <span class="string">&quot;:/docker/example&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.51</span> <span class="string">external</span></span><br><span class="line"><span class="string">如果设置为true，则指定已在Compose之外创建此卷。docker-compose</span> <span class="string">up不会尝试创建它，如果它不存在则引发错误。</span></span><br><span class="line"><span class="string">在下面的示例中，[projectname]_dataCompose</span> <span class="string">不是尝试创建一个被调用的卷，而是</span> <span class="string">查找简单调用的现有卷data并将其挂载到db服务的容器中。</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">data:/var/lib/postgresql/data</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">data:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line"><span class="string">还可以在Compose文件中与用于引用它的名称分别指定卷的名称：</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">data:</span></span><br><span class="line">    <span class="attr">external:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">actual-name-of-volume</span></span><br><span class="line"></span><br><span class="line"><span class="number">2.52</span> <span class="string">name</span></span><br><span class="line"><span class="string">为此卷设置自定义名称。name字段可用于引用包含特殊字符的卷。该名称按原样使用，不会使用堆栈名称作为范围。</span></span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">data:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">my-app-data</span></span><br><span class="line"><span class="string">它也可以与external参数一起使用：</span> </span><br><span class="line"><span class="attr">version:</span> <span class="string">&quot;3.7&quot;</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">data:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">my-app-data</span></span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line"><span class="number">3.01</span> <span class="string">命令使用</span></span><br><span class="line"><span class="string">命令对象与格式</span></span><br><span class="line"></span><br><span class="line"><span class="string">对于</span> <span class="string">Compose</span> <span class="string">来说，大部分命令的对象既可以是项目本身，也可以指定为项目中的服务或者容器。若没有特别说明，命令</span></span><br><span class="line"></span><br><span class="line"><span class="string">对象将是项目，这意味着项目中所有的服务都会受到命令影响</span></span><br><span class="line"></span><br><span class="line"><span class="string">执行</span> <span class="string">docker-compose</span> [<span class="string">COMMAND</span>] <span class="string">--help</span> <span class="string">或者</span> <span class="string">docker-compose</span> <span class="string">help</span> [<span class="string">COMMAND</span>] <span class="string">可以查看具体某个命令的使用格式。如下：</span></span><br><span class="line"></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">--help</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="string">docker-compose命令的基本使用格式是：</span></span><br><span class="line"></span><br><span class="line"><span class="string">docker-compose</span> [<span class="string">-f=&lt;arg&gt;...</span>] [<span class="string">options</span>] [<span class="string">COMMAND</span>] [<span class="string">ARGS...</span>]</span><br><span class="line"><span class="string">命令选项</span></span><br><span class="line"></span><br><span class="line"><span class="string">-f,</span> <span class="string">--file</span> <span class="string">FILE</span> <span class="string">指定使用的</span> <span class="string">Compose</span> <span class="string">模板文件，默认为</span> <span class="string">docker-compose.yml，可以多次指定；</span></span><br><span class="line"><span class="string">-p,</span> <span class="string">--project-name</span> <span class="string">NAME</span> <span class="string">指定项目名称，默认将使用所在目录名称作为项目名；</span></span><br><span class="line"><span class="string">--x-networking</span> <span class="string">使用</span> <span class="string">Docker</span> <span class="string">的可拔插网络后端特性；</span></span><br><span class="line"><span class="string">--x-network-driver</span> <span class="string">DRIVER</span> <span class="string">指定网络后端的驱动，默认为</span> <span class="string">bridge；</span></span><br><span class="line"><span class="string">--verbose</span> <span class="string">输出更多调试信息；</span></span><br><span class="line"><span class="string">-v,</span> <span class="string">--version</span> <span class="string">打印版本并退出；</span></span><br><span class="line"><span class="string">常用命令</span></span><br><span class="line"></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">命令</span> <span class="string">--help</span>                    <span class="string">获得一个命令的帮助</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">up</span> <span class="string">-d</span> <span class="string">nginx</span>                    <span class="string">构建启动nignx容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">exec</span> <span class="string">nginx</span> <span class="string">bash</span>                <span class="string">登录到nginx容器中</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">down</span>                           <span class="string">此命令将会停止</span> <span class="string">up</span> <span class="string">命令所启动的容器，并移除网络</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">ps</span>                             <span class="string">列出项目中目前的所有容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">restart</span> <span class="string">nginx</span>                  <span class="string">重新启动nginx容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">build</span> <span class="string">nginx</span>                    <span class="string">构建镜像</span> </span><br><span class="line"><span class="string">docker-compose</span> <span class="string">build</span> <span class="string">--no-cache</span> <span class="string">nginx</span>         <span class="string">不带缓存的构建</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">top</span>                            <span class="string">查看各个服务容器内运行的进程</span> </span><br><span class="line"><span class="string">docker-compose</span> <span class="string">logs</span> <span class="string">-f</span> <span class="string">nginx</span>                  <span class="string">查看nginx的实时日志</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">images</span>                         <span class="string">列出</span> <span class="string">Compose</span> <span class="string">文件包含的镜像</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">config</span>                         <span class="string">验证文件配置，当配置正确时，不输出任何内容，当文件配置错误，输出错误信息。</span> </span><br><span class="line"><span class="string">docker-compose</span> <span class="string">events</span> <span class="string">--json</span> <span class="string">nginx</span>            <span class="string">以json的形式输出nginx的docker日志</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">pause</span> <span class="string">nginx</span>                    <span class="string">暂停nignx容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">unpause</span> <span class="string">nginx</span>                  <span class="string">恢复ningx容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">rm</span> <span class="string">nginx</span>                       <span class="string">删除容器（删除前必须关闭容器，执行stop）</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">stop</span> <span class="string">nginx</span>                     <span class="string">停止nignx容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">start</span> <span class="string">nginx</span>                    <span class="string">启动nignx容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">restart</span> <span class="string">nginx</span>                  <span class="string">重启项目中的nignx容器</span></span><br><span class="line"><span class="string">docker-compose</span> <span class="string">run</span> <span class="string">--no-deps</span> <span class="string">--rm</span> <span class="string">php-fpm</span> <span class="string">php</span> <span class="string">-v</span>   <span class="string">在php-fpm中不启动关联容器，并容器执行php</span> <span class="string">-v</span> <span class="string">执行完成后删除容器</span></span><br><span class="line"><span class="number">4.01</span> <span class="string">文章参考：</span></span><br><span class="line"><span class="string">https://www.cnblogs.com/wutao666/p/11332186.html</span></span><br><span class="line"><span class="string">https://www.sitven.cn/blog/106</span></span><br></pre></td></tr></table></figure> 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/docker/" rel="tag">docker</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
  </article>
  

  
  <nav class="page-nav">
    
    <a class="extend prev" rel="prev" href="/page/10/">上一页</a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/9/">9</a><a class="page-number" href="/page/10/">10</a><span class="page-number current">11</span><a class="page-number" href="/page/12/">12</a><a class="page-number" href="/page/13/">13</a><a class="page-number" href="/page/14/">14</a><a class="extend next" rel="next" href="/page/12/">下一页</a>
  </nav>
  
</section>
</div>

      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> TzWind
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="Hexo"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://www.baidu.com">百度</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>